/*-
 * Copyright (c) 2009-2010 Alexander Egorenkov <egorenar@gmail.com>
 * Copyright (c) 2009 Damien Bergamini <damien.bergamini@free.fr>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/taskqueue.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/firmware.h>

#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>

#include <net/bpf.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>

#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_input.h>
#include <net80211/ieee80211_radiotap.h>
#include <net80211/ieee80211_regdomain.h>

#include <dev/ral/rt2860reg.h>
#include <dev/ral/rt2860var.h>

/*
 * Defines and macros
 */
#define RT2860_MAX_AGG_SIZE						3840

#define RT2860_TX_DATA_SEG0_SIZE				\
	(sizeof(struct rt2860_txwi) + sizeof(struct ieee80211_qosframe_addr4))

#define RT2860_NOISE_FLOOR						-95

#define IEEE80211_HAS_ADDR4(wh)					\
	(((wh)->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS)

#define	RT2860_MS(_v, _f)			(((_v) & _f) >> _f##_S)
#define	RT2860_SM(_v, _f)			(((_v) << _f##_S) & _f)

#define RT2860_TX_WATCHDOG_TIMEOUT				5

#define RUN_AID2WCID(aid)					((aid) & 0xff)
#define RT2860_WCID_RESERVED					0xff
#define RT2860_WCID_MCAST						0xf7

#define LED_CFG_LED_POLARITY	(1<<30)
#define LED_CFG_Y_LED_MODE_ONTX	(1<<28)
#define LED_CFG_G_LED_MODE_ONTX	(1<<26)
#define LED_CFG_R_LED_MODE_ONTX	(1<<24)
#define LED_CFG_SLOW_BLK_TIME 	(0x03<<16) /* sec */
#define LED_CFG_LED_OFF_TIME 	(0x1e<<8) /* msec */
#define LED_CFG_LED_ON_TIME 	(0x46) /* msec */

static void rt2872_rf_set_chan(struct rt2860_softc *sc,
			       struct ieee80211_channel *c);

/*
 * Static function prototypes
 */
static void rt2860_init_channels(struct rt2860_softc *sc);
static void rt2860_init_channels_ht40(struct rt2860_softc *sc);
static void rt2860_init_locked(void *priv);
static void rt2860_init(void *priv);
static int rt2860_init_bbp(struct rt2860_softc *sc);
static void rt2860_stop_locked(void *priv);
static void rt2860_stop(void *priv);
static void rt2860_start(struct ifnet *ifp);
static int rt2860_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data);
static struct ieee80211vap *rt2860_vap_create(struct ieee80211com *ic,
	const char name[IFNAMSIZ], int unit, enum ieee80211_opmode, int flags,
	const uint8_t bssid[IEEE80211_ADDR_LEN],
	const uint8_t mac[IEEE80211_ADDR_LEN]);
static void rt2860_vap_delete(struct ieee80211vap *vap);
static int rt2860_vap_reset(struct ieee80211vap *vap, u_long cmd);
static int rt2860_vap_newstate(struct ieee80211vap *vap,
	enum ieee80211_state nstate, int arg);

#ifndef RT2860_NO_HW_CRYPTO
static void rt2860_vap_key_update_begin(struct ieee80211vap *vap);
static void rt2860_vap_key_update_end(struct ieee80211vap *vap);
static int rt2860_vap_key_set(struct ieee80211vap *vap,
	const struct ieee80211_key *k, const uint8_t mac[IEEE80211_ADDR_LEN]);
static int rt2860_vap_key_delete(struct ieee80211vap *vap,
	const struct ieee80211_key *k);
#endif

static void rt2860_vap_update_beacon(struct ieee80211vap *vap, int what);
static int rt2860_media_change(struct ifnet *ifp);
static struct ieee80211_node *rt2860_node_alloc(struct ieee80211vap *vap,
	const uint8_t mac[IEEE80211_ADDR_LEN]);
static void rt2860_node_cleanup(struct ieee80211_node *ni);
static void rt2860_node_getmimoinfo(const struct ieee80211_node *ni,
	struct ieee80211_mimo_info *mi);
static int rt2860_setregdomain(struct ieee80211com *ic,
	struct ieee80211_regdomain *reg,
	int nchans, struct ieee80211_channel chans[]);
static void rt2860_getradiocaps(struct ieee80211com *ic,
	int maxchans, int *nchans, struct ieee80211_channel chans[]);
static void rt2860_scan_start(struct ieee80211com *ic);
static void rt2860_scan_end(struct ieee80211com *ic);
static void rt2860_set_channel(struct ieee80211com *ic);
static void rt2860_newassoc(struct ieee80211_node *ni, int isnew);
static void rt2860_updateslot(struct ifnet *ifp);
static void rt2860_update_promisc(struct ifnet *ifp);
static void rt2860_update_mcast(struct ifnet *ifp);
static int rt2860_wme_update(struct ieee80211com *ic);
static int rt2860_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
	const struct ieee80211_bpf_params *params);
static int rt2860_recv_action(struct ieee80211_node *ni,
	const struct ieee80211_frame *wh,
	const uint8_t *frm, const uint8_t *efrm);
static int rt2860_send_action(struct ieee80211_node *ni,
	int cat, int act, void *sa);
static int rt2860_addba_response(struct ieee80211_node *ni,
	struct ieee80211_tx_ampdu *tap,
	int status, int baparamset, int batimeout);
static void rt2860_addba_stop(struct ieee80211_node *ni,
	struct ieee80211_tx_ampdu *tap);
static int rt2860_ampdu_rx_start(struct ieee80211_node *ni,
	struct ieee80211_rx_ampdu *rap,
	int baparamset, int batimeout, int baseqctl);
static void rt2860_ampdu_rx_stop(struct ieee80211_node *ni,
	struct ieee80211_rx_ampdu *rap);
#ifdef	RT2860_SEND_BAR_WORKAROUND
static int rt2860_send_bar(struct ieee80211_node *ni,
	struct ieee80211_tx_ampdu *tap, ieee80211_seq seqno);
#endif
static void rt2860_amrr_update_iter_func(void *arg, struct ieee80211_node *ni);
static void rt2860_periodic(void *arg);
static void rt2860_tx_watchdog(void *arg);
static int rt2860_staid_alloc(struct rt2860_softc *sc, int aid);
static void rt2860_staid_delete(struct rt2860_softc *sc, int staid);
void rt2860_amrr_init(struct rt2860_amrr *amrr, struct ieee80211vap *vap,
		      int ntxpath, int min_success_threshold,
		      int max_success_threshold, int msecs);
void rt2860_amrr_cleanup(struct rt2860_amrr *amrr);
void rt2860_amrr_node_init(struct rt2860_amrr *amrr,
    struct rt2860_amrr_node *amrr_node, struct ieee80211_node *ni);
int rt2860_amrr_choose(struct ieee80211_node *ni,
	struct rt2860_amrr_node *amrr_node);

static __inline void
rt2860_amrr_tx_complete(struct rt2860_amrr_node *amrr_node, int ok, int retries)
{

	amrr_node->txcnt++;

	if (ok)
		amrr_node->success++;

	amrr_node->retrycnt += retries;
}

static __inline void
rt2860_amrr_tx_update(struct rt2860_amrr_node *amrr_node, int txcnt,
		      int success, int retrycnt)
{

	amrr_node->txcnt = txcnt;
	amrr_node->success = success;
	amrr_node->retrycnt = retrycnt;
}

static void rt2860_asic_set_bssid(struct rt2860_softc *sc,
				  const uint8_t *bssid);
static void rt2860_asic_set_macaddr(struct rt2860_softc *sc,
				    const uint8_t *addr);
static void rt2860_asic_enable_tsf_sync(struct rt2860_softc *sc);
static void rt2860_asic_disable_tsf_sync(struct rt2860_softc *sc);
static void rt2860_asic_enable_mrr(struct rt2860_softc *sc);
static void rt2860_asic_set_txpreamble(struct rt2860_softc *sc);
static void rt2860_asic_set_basicrates(struct rt2860_softc *sc);
static void rt2860_asic_update_rtsthreshold(struct rt2860_softc *sc);
static void rt2860_asic_update_txpower(struct rt2860_softc *sc);
static void rt2860_asic_update_promisc(struct rt2860_softc *sc);
static void rt2860_asic_updateprot(struct rt2860_softc *sc);
static void rt2860_asic_updateslot(struct rt2860_softc *sc);
static void rt2860_asic_wme_update(struct rt2860_softc *sc);
static void rt2860_asic_update_beacon(struct rt2860_softc *sc,
		    struct ieee80211vap *, struct rt2860_txwi *);
static void rt2860_asic_clear_keytables(struct rt2860_softc *sc);
static void rt2860_asic_add_ba_session(struct rt2860_softc *sc,
				       uint8_t wcid, int tid);
static void rt2860_asic_del_ba_session(struct rt2860_softc *sc,
				       uint8_t wcid, int tid);
void rt2860_led_brightness(struct rt2860_softc *sc, uint8_t brightness);
void rt2860_led_cmd(struct rt2860_softc *sc, uint8_t cmd);
const char *rt2860_rf_name(int rf_rev);
void rt2860_rf_select_chan_group(struct rt2860_softc *sc,
	struct ieee80211_channel *c);
void rt2860_rf_set_chan(struct rt2860_softc *sc,
	struct ieee80211_channel *c);
uint8_t rt3090_rf_read(struct rt2860_softc *sc, uint8_t reg);
void rt3090_rf_write(struct rt2860_softc *sc, uint8_t reg, uint8_t val);
void rt3090_set_chan(struct rt2860_softc *sc, u_int chan);
int rt3090_rf_init(struct rt2860_softc *sc);
void rt3090_set_rx_antenna(struct rt2860_softc *, int);
void rt3090_rf_wakeup(struct rt2860_softc *sc);
int rt3090_filter_calib(struct rt2860_softc *sc, uint8_t init, uint8_t target,
			uint8_t *val);
void rt3090_rf_setup(struct rt2860_softc *sc);
void rt2860_read_eeprom(struct rt2860_softc *sc);
uint32_t rt2860_read_eeprom_txpow_rate_add_delta(uint32_t txpow_rate,
						 int8_t delta);
void rt2860_io_rf_load_defaults(struct rt2860_softc *sc);
uint16_t rt2860_io_eeprom_read(struct rt2860_softc *sc, uint16_t addr);
uint8_t rt2860_io_bbp_read(struct rt2860_softc *sc, uint8_t reg);
void rt2860_io_bbp_write(struct rt2860_softc *sc, uint8_t reg, uint8_t val);
void rt2860_io_rf_write(struct rt2860_softc *sc, uint8_t reg, uint32_t val);
int32_t rt2860_io_rf_read(struct rt2860_softc *sc, uint8_t reg);
void rt2860_io_mcu_cmd(struct rt2860_softc *sc, uint8_t cmd,
		       uint8_t token, uint16_t arg);
int rt2860_io_mcu_cmd_check(struct rt2860_softc *sc, uint8_t cid);
int rt2860_load_microcode(struct rt2860_softc *sc);
static int rt2860_beacon_alloc(struct rt2860_softc *sc,
			       struct ieee80211vap *vap);
static uint8_t rt2860_rxrate(struct rt2860_rxwi *rxwi);
static uint8_t rt2860_maxrssi_rxpath(struct rt2860_softc *sc,
				     const struct rt2860_rxwi *rxwi);
static int8_t rt2860_rssi2dbm(struct rt2860_softc *sc,
			      uint8_t rssi, uint8_t rxpath);
static uint8_t rt2860_rate2mcs(uint8_t rate);
static int rt2860_tx_mgmt(struct rt2860_softc *sc,
			  struct mbuf *m, struct ieee80211_node *ni, int qid);
static int rt2860_tx_data(struct rt2860_softc *sc,
			  struct mbuf *m, struct ieee80211_node *ni, int qid);

static void rt2860_reset_dma(struct rt2860_softc *sc);
static void rt2860_fifo_sta_full_intr(struct rt2860_softc *sc);
static void rt2860_rx_intr(struct rt2860_softc *sc);
static void rt2860_tx_intr(struct rt2860_softc *sc, int qid);
static void rt2860_rx_done_task(void *context, int pending);
static void rt2860_tx_done_task(void *context, int pending);
static void rt2860_fifo_sta_full_task(void *context, int pending);
static void rt2860_periodic_task(void *context, int pending);
static int rt2860_rx_eof(struct rt2860_softc *sc, int limit);
static void rt2860_tx_eof(struct rt2860_softc *sc,
	struct rt2860_softc_tx_ring *ring);
static void rt2860_update_stats(struct rt2860_softc *sc);
static void rt2860_bbp_tuning(struct rt2860_softc *sc);
static void rt2860_watchdog(struct rt2860_softc *sc);
static void rt2860_drain_fifo_stats(struct rt2860_softc *sc);
static void rt2860_update_raw_counters(struct rt2860_softc *sc);
static void rt2860_intr_enable(struct rt2860_softc *sc, uint32_t intr_mask);
static void rt2860_intr_disable(struct rt2860_softc *sc, uint32_t intr_mask);
static int rt2860_txrx_enable(struct rt2860_softc *sc);
static int rt2860_alloc_rx_ring(struct rt2860_softc *sc,
	struct rt2860_softc_rx_ring *ring);
static void rt2860_reset_rx_ring(struct rt2860_softc *sc,
	struct rt2860_softc_rx_ring *ring);
static void rt2860_free_rx_ring(struct rt2860_softc *sc,
	struct rt2860_softc_rx_ring *ring);
static int rt2860_alloc_tx_ring(struct rt2860_softc *sc,
	struct rt2860_softc_tx_ring *ring, int qid);
static void rt2860_reset_tx_ring(struct rt2860_softc *sc,
	struct rt2860_softc_tx_ring *ring);
static void rt2860_free_tx_ring(struct rt2860_softc *sc,
	struct rt2860_softc_tx_ring *ring);
static void rt2860_dma_map_addr(void *arg, bus_dma_segment_t *segs,
	int nseg, int error);
static void rt2860_sysctl_attach(struct rt2860_softc *sc);

/*
 * Static variables
 */

SYSCTL_NODE(_hw, OID_AUTO, ral, CTLFLAG_RD, 0, "RT2860 driver parameters");

static int rt2860_tx_stbc = 1;
SYSCTL_INT(_hw_ral, OID_AUTO, tx_stbc, CTLFLAG_RW, &rt2860_tx_stbc, 0,
	   "RT2860 Tx STBC");
TUNABLE_INT("hw.ral.tx_stbc", &rt2860_tx_stbc);

#ifdef RT2860_DEBUG
static int rt2860_debug = 0;
SYSCTL_INT(_hw_ral, OID_AUTO, debug, CTLFLAG_RW, &rt2860_debug, 0,
	   "RT2860 debug level");
TUNABLE_INT("hw.ral.debug", &rt2860_debug);
#endif

static const struct {
	uint32_t	reg;
	uint32_t	val;
} rt2860_def_mac[] = {
	RT2860_DEF_MAC
};

static const struct {
	uint8_t	reg;
	uint8_t	val;
} rt2860_def_bbp[] = {
	RT2860_DEF_BBP
};

static const struct rfprog {
	uint8_t		chan;
	uint32_t	r1, r2, r3, r4;
} rt2860_rf2850[] = {
	RT2860_RF2850
};

struct {
	uint8_t	n, r, k;
} rt3090_freqs[] = {
	RT3070_RF3052
};

static const struct {
	uint8_t	reg;
	uint8_t	val;
}  rt3090_def_rf[] = {
	RT3070_DEF_RF
};

/*
 * rt2860_attach
 */
int
rt2860_attach(device_t dev, int id)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	struct ieee80211com *ic;
	int error, ntries, i;

	sc = device_get_softc(dev);

	sc->sc_dev = dev;
	sc->id = id;

	mtx_init(&sc->sc_mtx, device_get_nameunit(dev),
		MTX_NETWORK_LOCK, MTX_DEF | MTX_RECURSE);

	sc->tx_stbc = rt2860_tx_stbc;

#ifdef RT2860_DEBUG
	sc->debug = rt2860_debug;

	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
		SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
		"debug", CTLFLAG_RW, &sc->debug, 0, "rt2860 debug level");
#endif

	RT2860_DPRINTF(sc, RT2860_DEBUG_ANY, "attaching\n");

	/* wait for NIC to initialize */
	for (ntries = 0; ntries < 100; ntries++) {
		sc->mac_rev = RAL_READ(sc, RT2860_REG_MAC_CSR0);
		if (sc->mac_rev != 0x00000000 && sc->mac_rev != 0xffffffff)
			break;

		DELAY(10);
	}

	if (ntries == 100) {
		device_printf(dev, "timeout waiting for NIC to initialize\n");
		error = EIO;
		goto fail;
	}

	rt2860_read_eeprom(sc);

	device_printf(sc->sc_dev, "MAC/BBP RT2860 (rev 0x%08x), RF %s\n",
	    sc->mac_rev,rt2860_rf_name(sc->rf_rev));

	RT2860_SOFTC_LOCK(sc);
	/* clear key tables */
	rt2860_asic_clear_keytables(sc);
	RT2860_SOFTC_UNLOCK(sc);

	/* allocate Tx and Rx rings */
	for (i = 0; i < RT2860_SOFTC_TX_RING_COUNT; i++) {
		error = rt2860_alloc_tx_ring(sc, &sc->tx_ring[i], i);
		if (error != 0) {
			device_printf(sc->sc_dev, "could not allocate Tx "
			    "ring #%d\n", i);
			goto fail;
		}
	}

	sc->tx_ring_mgtqid = 5;

	error = rt2860_alloc_rx_ring(sc, &sc->rx_ring);
	if (error != 0) {
		device_printf(sc->sc_dev, "could not allocate Rx ring\n");
		goto fail;
	}

	callout_init(&sc->periodic_ch, 0);
	callout_init_mtx(&sc->tx_watchdog_ch, &sc->sc_mtx, 0);

	ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211);
	if (ifp == NULL) {
		device_printf(sc->sc_dev, "could not if_alloc()\n");
		error = ENOMEM;
		goto fail;
	}

	ifp->if_softc = sc;

	if_initname(ifp, device_get_name(dev), device_get_unit(dev));

	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;

	ifp->if_init = rt2860_init;
	ifp->if_ioctl = rt2860_ioctl;
	ifp->if_start = rt2860_start;

	IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
	ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN;
	IFQ_SET_READY(&ifp->if_snd);

	ic = ifp->if_l2com;

	ic->ic_ifp = ifp;

	ic->ic_phytype = IEEE80211_T_HT;
	ic->ic_opmode = IEEE80211_M_STA;

	ic->ic_caps = IEEE80211_C_MONITOR |
		IEEE80211_C_IBSS |
		IEEE80211_C_STA |
		IEEE80211_C_AHDEMO |
		IEEE80211_C_HOSTAP |
		IEEE80211_C_WDS |
		IEEE80211_C_MBSS |
		IEEE80211_C_BGSCAN |
		IEEE80211_C_TXPMGT |
		IEEE80211_C_SHPREAMBLE |
		IEEE80211_C_SHSLOT |
		IEEE80211_C_TXFRAG |
		IEEE80211_C_BURST |
		IEEE80211_C_WME |
		IEEE80211_C_WPA;

#ifndef RT2860_NO_HW_CRYPTO
	ic->ic_cryptocaps |= IEEE80211_CRYPTO_WEP |
		IEEE80211_CRYPTO_TKIP |
		IEEE80211_CRYPTO_TKIPMIC |
		IEEE80211_CRYPTO_AES_CCM;
#endif
	ic->ic_htcaps = IEEE80211_HTC_HT |
		IEEE80211_HTC_AMSDU |		/* A-MSDU Tx */
		IEEE80211_HTC_AMPDU |		/* A-MPDU Tx */
		IEEE80211_HTC_SMPS |		/* MIMO power save */
		IEEE80211_HTCAP_MAXAMSDU_3839 |	/* max. A-MSDU Rx length */
		IEEE80211_HTCAP_CHWIDTH40 |	/* HT 40MHz channel width */
		IEEE80211_HTCAP_GREENFIELD |	/* HT greenfield */
		IEEE80211_HTCAP_SHORTGI20 |	/* HT 20MHz short GI */
		IEEE80211_HTCAP_SHORTGI40 |	/* HT 40MHz short GI */
		IEEE80211_HTCAP_SMPS_OFF;	/* MIMO power save disabled */

	/* spatial streams */
	if (sc->nrxpath == 2)
		ic->ic_htcaps |= IEEE80211_HTCAP_RXSTBC_2STREAM;
	else if (sc->nrxpath == 3)
		ic->ic_htcaps |= IEEE80211_HTCAP_RXSTBC_3STREAM;
	else
		ic->ic_htcaps |= IEEE80211_HTCAP_RXSTBC_1STREAM;

	if (sc->ntxpath > 1)
		ic->ic_htcaps |= IEEE80211_HTCAP_TXSTBC;

	/* delayed BA */
	if (sc->mac_rev != 0x28600100)
		ic->ic_htcaps |= IEEE80211_HTCAP_DELBA;

	/* init channels */
	ic->ic_nchans = 0;

	rt2860_init_channels(sc);
	rt2860_init_channels_ht40(sc);

	ieee80211_ifattach(ic, sc->mac_addr);

	ic->ic_vap_create = rt2860_vap_create;
	ic->ic_vap_delete = rt2860_vap_delete;

	ic->ic_node_alloc = rt2860_node_alloc;
	sc->node_cleanup = ic->ic_node_cleanup;
	ic->ic_node_cleanup = rt2860_node_cleanup;

	ic->ic_node_getmimoinfo = rt2860_node_getmimoinfo;
	ic->ic_setregdomain = rt2860_setregdomain;
	ic->ic_getradiocaps = rt2860_getradiocaps;
	ic->ic_scan_start = rt2860_scan_start;
	ic->ic_scan_end = rt2860_scan_end;
	ic->ic_set_channel = rt2860_set_channel;
	ic->ic_newassoc = rt2860_newassoc;
	ic->ic_updateslot = rt2860_updateslot;
	ic->ic_update_promisc = rt2860_update_promisc;
	ic->ic_update_mcast = rt2860_update_mcast;
	ic->ic_wme.wme_update = rt2860_wme_update;
	ic->ic_raw_xmit = rt2860_raw_xmit;

	sc->recv_action = ic->ic_recv_action;
	ic->ic_recv_action = rt2860_recv_action;

	sc->send_action = ic->ic_send_action;
	ic->ic_send_action = rt2860_send_action;

	sc->addba_response = ic->ic_addba_response;
	ic->ic_addba_response = rt2860_addba_response;

	sc->addba_stop = ic->ic_addba_stop;
	ic->ic_addba_stop = rt2860_addba_stop;

	sc->ampdu_rx_start = ic->ic_ampdu_rx_start;
	ic->ic_ampdu_rx_start = rt2860_ampdu_rx_start;

	sc->ampdu_rx_stop = ic->ic_ampdu_rx_stop;
	ic->ic_ampdu_rx_stop = rt2860_ampdu_rx_stop;

	/* hardware requires padding between 802.11 frame header and body */
	ic->ic_flags |= IEEE80211_F_DATAPAD | IEEE80211_F_DOTH;
	ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS;

	ieee80211_radiotap_attach(ic,
	    &sc->txtap.ihdr, sizeof(sc->txtap),
		RT2860_SOFTC_TX_RADIOTAP_PRESENT,
	    &sc->rxtap.ihdr, sizeof(sc->rxtap),
		RT2860_SOFTC_RX_RADIOTAP_PRESENT);

	/* init task queue */
	TASK_INIT(&sc->rx_done_task, 0, rt2860_rx_done_task, sc);
	TASK_INIT(&sc->tx_done_task, 0, rt2860_tx_done_task, sc);
	TASK_INIT(&sc->fifo_sta_full_task, 0, rt2860_fifo_sta_full_task, sc);
	TASK_INIT(&sc->periodic_task, 0, rt2860_periodic_task, sc);

	sc->rx_process_limit = 100;

	sc->taskqueue = taskqueue_create("rt2860_taskq", M_NOWAIT,
	    taskqueue_thread_enqueue, &sc->taskqueue);

	taskqueue_start_threads(&sc->taskqueue, 1, PI_NET, "%s taskq",
	    device_get_nameunit(sc->sc_dev));

	rt2860_sysctl_attach(sc);

	if (bootverbose)
		ieee80211_announce(ic);

	return 0;

fail:

	/* free Tx and Rx rings */
	for (i = 0; i < RT2860_SOFTC_TX_RING_COUNT; i++)
		rt2860_free_tx_ring(sc, &sc->tx_ring[i]);

	rt2860_free_rx_ring(sc, &sc->rx_ring);

	mtx_destroy(&sc->sc_mtx);

	if (sc->mem != NULL)
		bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);

	if (sc->irq != NULL)
		bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq);

	return error;
}

/*
 * rt2860_detach
 */
int
rt2860_detach(void *xsc)
{
	struct rt2860_softc *sc = xsc;
	struct ifnet *ifp;
	struct ieee80211com *ic;
	int i;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	RT2860_DPRINTF(sc, RT2860_DEBUG_ANY, "detaching\n");

	RT2860_SOFTC_LOCK(sc);

	ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);

	callout_stop(&sc->periodic_ch);
	callout_stop(&sc->tx_watchdog_ch);

	taskqueue_drain(sc->taskqueue, &sc->rx_done_task);
	taskqueue_drain(sc->taskqueue, &sc->tx_done_task);
	taskqueue_drain(sc->taskqueue, &sc->fifo_sta_full_task);
	taskqueue_drain(sc->taskqueue, &sc->periodic_task);

	/* free Tx and Rx rings */
	for (i = 0; i < RT2860_SOFTC_TX_RING_COUNT; i++)
		rt2860_free_tx_ring(sc, &sc->tx_ring[i]);

	rt2860_free_rx_ring(sc, &sc->rx_ring);

	RT2860_SOFTC_UNLOCK(sc);

	ieee80211_ifdetach(ic);

	if_free(ifp);

	taskqueue_free(sc->taskqueue);

	mtx_destroy(&sc->sc_mtx);

	return 0;
}

/*
 * rt2860_shutdown
 */
void
rt2860_shutdown(void *xsc)
{
	struct rt2860_softc *sc = xsc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_ANY, "shutting down\n");

	rt2860_stop(sc);

	sc->flags &= ~RT2860_SOFTC_FLAGS_UCODE_LOADED;

	return;
}

/*
 * rt2860_suspend
 */
void
rt2860_suspend(void *xsc)
{
	struct rt2860_softc *sc = xsc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_ANY, "suspending\n");

	rt2860_stop(sc);

	sc->flags &= ~RT2860_SOFTC_FLAGS_UCODE_LOADED;

	return;
}

/*
 * rt2860_resume
 */
void
rt2860_resume(void *xsc)
{
	struct rt2860_softc *sc = xsc;
	struct ifnet *ifp;

	ifp = sc->sc_ifp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_ANY, "resuming\n");

	if (ifp->if_flags & IFF_UP)
		rt2860_init(sc);

	return;
}

/*
 * rt2860_init_channels
 */
static void
rt2860_init_channels(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211_channel *c;
	int i, flags;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	/* set supported channels for 2GHz band */
	for (i = 1; i <= 14; i++) {
		c = &ic->ic_channels[ic->ic_nchans++];
		flags = IEEE80211_CHAN_B;

		c->ic_freq = ieee80211_ieee2mhz(i, flags);
		c->ic_ieee = i;
		c->ic_flags = flags;

		c = &ic->ic_channels[ic->ic_nchans++];
		flags = IEEE80211_CHAN_B | IEEE80211_CHAN_HT20;

		c->ic_freq = ieee80211_ieee2mhz(i, flags);
		c->ic_ieee = i;
		c->ic_flags = flags;

		c = &ic->ic_channels[ic->ic_nchans++];
		flags = IEEE80211_CHAN_G;

		c->ic_freq = ieee80211_ieee2mhz(i, flags);
		c->ic_ieee = i;
		c->ic_flags = flags;

		c = &ic->ic_channels[ic->ic_nchans++];
		flags = IEEE80211_CHAN_G | IEEE80211_CHAN_HT20;

		c->ic_freq = ieee80211_ieee2mhz(i, flags);
		c->ic_ieee = i;
		c->ic_flags = flags;
	}

	/* set supported channels for 5GHz band */
	if (sc->rf_rev == RT2860_RF_2850 ||
		sc->rf_rev == RT2860_RF_2750 ||
		sc->rf_rev == RT3070_RF_3052) {
		for (i = 36; i <= 64; i += 4) {
			c = &ic->ic_channels[ic->ic_nchans++];
			flags = IEEE80211_CHAN_A;

			c->ic_freq = ieee80211_ieee2mhz(i, flags);
			c->ic_ieee = i;
			c->ic_flags = flags;

			c = &ic->ic_channels[ic->ic_nchans++];
			flags = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20;

			c->ic_freq = ieee80211_ieee2mhz(i, flags);
			c->ic_ieee = i;
			c->ic_flags = flags;
		}

		for (i = 100; i <= 140; i += 4) {
			c = &ic->ic_channels[ic->ic_nchans++];
			flags = IEEE80211_CHAN_A;

			c->ic_freq = ieee80211_ieee2mhz(i, flags);
			c->ic_ieee = i;
			c->ic_flags = flags;

			c = &ic->ic_channels[ic->ic_nchans++];
			flags = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20;

			c->ic_freq = ieee80211_ieee2mhz(i, flags);
			c->ic_ieee = i;
			c->ic_flags = flags;
		}

		for (i = 149; i <= 165; i += 4) {
			c = &ic->ic_channels[ic->ic_nchans++];
			flags = IEEE80211_CHAN_A;

			c->ic_freq = ieee80211_ieee2mhz(i, flags);
			c->ic_ieee = i;
			c->ic_flags = flags;

			c = &ic->ic_channels[ic->ic_nchans++];
			flags = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20;

			c->ic_freq = ieee80211_ieee2mhz(i, flags);
			c->ic_ieee = i;
			c->ic_flags = flags;
		}
	}
}

/*
 * rt2860_init_channels_ht40
 */
static void
rt2860_init_channels_ht40(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211_channel *c, *cent, *ext;
	int i, flags;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	/* set supported channels for 2GHz band */
	for (i = 1; i <= 14; i++) {
		flags = IEEE80211_CHAN_G | IEEE80211_CHAN_HT40;

		/* find the center channel */
		cent = ieee80211_find_channel_byieee(ic, i,
			flags & ~IEEE80211_CHAN_HT);
		if (cent == NULL) {
			device_printf(sc->sc_dev, "skip channel %d, "
				      "could not find center channel\n", i);
			continue;
		}

		/* find the extension channel */
		ext = ieee80211_find_channel(ic, cent->ic_freq + 20,
			flags & ~IEEE80211_CHAN_HT);
		if (ext == NULL) {
			device_printf(sc->sc_dev, "skip channel %d, "
				      "could not find extension channel\n", i);
			continue;
		}

		c = &ic->ic_channels[ic->ic_nchans++];

		*c = *cent;
		c->ic_extieee = ext->ic_ieee;
		c->ic_flags &= ~IEEE80211_CHAN_HT;
		c->ic_flags |= IEEE80211_CHAN_HT40U;

		c = &ic->ic_channels[ic->ic_nchans++];

		*c = *ext;
		c->ic_extieee = cent->ic_ieee;
		c->ic_flags &= ~IEEE80211_CHAN_HT;
		c->ic_flags |= IEEE80211_CHAN_HT40D;
	}

	/* set supported channels for 5GHz band */
	if (sc->rf_rev == RT2860_RF_2850 ||
		sc->rf_rev == RT2860_RF_2750 ||
		sc->rf_rev == RT3070_RF_3052) {
		for (i = 36; i <= 64; i += 4) {
			flags = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40;

			/* find the center channel */
			cent = ieee80211_find_channel_byieee(ic, i,
				flags & ~IEEE80211_CHAN_HT);
			if (cent == NULL) {
				device_printf(sc->sc_dev, "skip channel %d, "
				    "could not find center channel\n", i);
				continue;
			}

			/* find the extension channel */

			ext = ieee80211_find_channel(ic, cent->ic_freq + 20,
				flags & ~IEEE80211_CHAN_HT);
			if (ext == NULL) {
				device_printf(sc->sc_dev, "skip channel %d, "
				    "could not find extension channel\n", i);
				continue;
			}

			c = &ic->ic_channels[ic->ic_nchans++];

			*c = *cent;
			c->ic_extieee = ext->ic_ieee;
			c->ic_flags &= ~IEEE80211_CHAN_HT;
			c->ic_flags |= IEEE80211_CHAN_HT40U;

			c = &ic->ic_channels[ic->ic_nchans++];

			*c = *ext;
			c->ic_extieee = cent->ic_ieee;
			c->ic_flags &= ~IEEE80211_CHAN_HT;
			c->ic_flags |= IEEE80211_CHAN_HT40D;
		}

		for (i = 100; i <= 140; i += 4) {
			flags = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40;

			/* find the center channel */
			cent = ieee80211_find_channel_byieee(ic, i,
				flags & ~IEEE80211_CHAN_HT);
			if (cent == NULL) {
				device_printf(sc->sc_dev, "skip channel %d, "
				    "could not find center channel\n", i);
				continue;
			}

			/* find the extension channel */
			ext = ieee80211_find_channel(ic, cent->ic_freq + 20,
				flags & ~IEEE80211_CHAN_HT);
			if (ext == NULL) {
				device_printf(sc->sc_dev, "skip channel %d, "
				    "could not find extension channel\n", i);
				continue;
			}

			c = &ic->ic_channels[ic->ic_nchans++];

			*c = *cent;
			c->ic_extieee = ext->ic_ieee;
			c->ic_flags &= ~IEEE80211_CHAN_HT;
			c->ic_flags |= IEEE80211_CHAN_HT40U;

			c = &ic->ic_channels[ic->ic_nchans++];

			*c = *ext;
			c->ic_extieee = cent->ic_ieee;
			c->ic_flags &= ~IEEE80211_CHAN_HT;
			c->ic_flags |= IEEE80211_CHAN_HT40D;
		}

		for (i = 149; i <= 165; i += 4) {
			flags = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40;

			/* find the center channel */

			cent = ieee80211_find_channel_byieee(ic, i,
				flags & ~IEEE80211_CHAN_HT);
			if (cent == NULL) {
				device_printf(sc->sc_dev, "skip channel %d, "
				    "could not find center channel\n", i);
				continue;
			}

			/* find the extension channel */

			ext = ieee80211_find_channel(ic, cent->ic_freq + 20,
				flags & ~IEEE80211_CHAN_HT);
			if (ext == NULL) {
				device_printf(sc->sc_dev, "skip channel %d, "
				    "could not find extension channel\n", i);
				continue;
			}

			c = &ic->ic_channels[ic->ic_nchans++];

			*c = *cent;
			c->ic_extieee = ext->ic_ieee;
			c->ic_flags &= ~IEEE80211_CHAN_HT;
			c->ic_flags |= IEEE80211_CHAN_HT40U;

			c = &ic->ic_channels[ic->ic_nchans++];

			*c = *ext;
			c->ic_extieee = cent->ic_ieee;
			c->ic_flags &= ~IEEE80211_CHAN_HT;
			c->ic_flags |= IEEE80211_CHAN_HT40D;
		}
	}
}

/*
 * rt2860_init_locked
 */
static void
rt2860_init_locked(void *priv)
{
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	struct ieee80211com *ic;
	int error, i, ntries;
	uint32_t tmp;
#if 0
	uint32_t stacnt[6];
#endif

	sc = priv;
	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	RT2860_DPRINTF(sc, RT2860_DEBUG_ANY, "initializing\n");

	RT2860_SOFTC_ASSERT_LOCKED(sc);

	if (sc->mac_rev != 0x28720200) {
	    if (!(sc->flags & RT2860_SOFTC_FLAGS_UCODE_LOADED)) {
		error = rt2860_load_microcode(sc);
		if (error != 0) {
			device_printf(sc->sc_dev,
			    "could not load 8051 microcode\n");
			goto fail;
		}
		sc->flags |= RT2860_SOFTC_FLAGS_UCODE_LOADED;
	    }
	} else {
		sc->flags |= RT2860_SOFTC_FLAGS_UCODE_LOADED;

		/* Blink every TX */
		RAL_WRITE(sc, RT2860_LED_CFG,
		    LED_CFG_LED_POLARITY |
		    LED_CFG_Y_LED_MODE_ONTX |
		    LED_CFG_G_LED_MODE_ONTX |
		    LED_CFG_R_LED_MODE_ONTX |
		    LED_CFG_SLOW_BLK_TIME |
		    LED_CFG_LED_OFF_TIME |
		    LED_CFG_LED_ON_TIME);
	}

	RAL_WRITE(sc, RT2860_PWR_PIN_CFG, 0x2);

	/* disable DMA engine */

	tmp = RAL_READ(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG);

	tmp &= 0xff0;
	tmp |= RT2860_REG_TX_WB_DDONE;

	RAL_WRITE(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG, tmp);

	RAL_WRITE(sc, RT2860_REG_SCHDMA_WPDMA_RST_IDX, 0xffffffff);

	/* PBF hardware reset */

	RAL_WRITE(sc, RT2860_REG_PBF_SYS_CTRL, 0xe1f);
	RAL_WRITE(sc, RT2860_REG_PBF_SYS_CTRL, 0xe00);

	/* wait while DMA engine is busy */

	for (ntries = 0; ntries < 100; ntries++) {
		tmp = RAL_READ(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG);
		if (!(tmp & (RT2860_REG_TX_DMA_BUSY | RT2860_REG_RX_DMA_BUSY)))
			break;

		DELAY(1000);
	}

	if (ntries == 100) {
		device_printf(sc->sc_dev, "timeout waiting for DMA engine\n");
		goto fail;
	}

	tmp &= 0xff0;
	tmp |= RT2860_REG_TX_WB_DDONE;

	RAL_WRITE(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG, tmp);

	/* reset Rx and Tx rings */

	tmp = RT2860_REG_RST_IDX_RX |
		RT2860_REG_RST_IDX_TX_MGMT |
		RT2860_REG_RST_IDX_TX_HCCA |
		RT2860_REG_RST_IDX_TX_AC3 |
		RT2860_REG_RST_IDX_TX_AC2 |
		RT2860_REG_RST_IDX_TX_AC1 |
		RT2860_REG_RST_IDX_TX_AC0;

	RAL_WRITE(sc, RT2860_REG_SCHDMA_WPDMA_RST_IDX, tmp);

	/* PBF hardware reset */

	RAL_WRITE(sc, RT2860_REG_PBF_SYS_CTRL, 0xe1f);
	RAL_WRITE(sc, RT2860_REG_PBF_SYS_CTRL, 0xe00);

	RAL_WRITE(sc, RT2860_PWR_PIN_CFG, 0x3);

	RAL_WRITE(sc, RT2860_MAC_SYS_CTRL,
		RT2860_REG_MAC_SRST | RT2860_REG_BBP_HRST);
	RAL_WRITE(sc, RT2860_MAC_SYS_CTRL, 0);

	/* init Tx power per rate */

	for (i = 0; i < RT2860_SOFTC_TXPOW_RATE_COUNT; i++) {
		if (sc->txpow_rate_20mhz[i] == 0xffffffff)
			continue;

		RAL_WRITE(sc, RT2860_REG_TX_PWR_CFG(i),
			sc->txpow_rate_20mhz[i]);
	}

	for (i = 0; i < nitems(rt2860_def_mac); i++)
		RAL_WRITE(sc, rt2860_def_mac[i].reg, rt2860_def_mac[i].val);

	/* wait while MAC is busy */

	for (ntries = 0; ntries < 100; ntries++) {
		if (!(RAL_READ(sc, RT2860_REG_STATUS_CFG) &
			(RT2860_REG_STATUS_TX_BUSY | RT2860_REG_STATUS_RX_BUSY)))
			break;

		DELAY(1000);
	}

	if (ntries == 100) {
		device_printf(sc->sc_dev, "timeout waiting for MAC\n");
		goto fail;
	}
	/* clear Host to MCU mailbox */

	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX_BBP_AGENT, 0);
	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX, 0);

	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_BOOT,
		RT2860_TOKEN_NO_INTR, 0);

	DELAY(1000);

	error = rt2860_init_bbp(sc);
	if (error != 0)
		goto fail;

	/* set up maximum buffer sizes */

	tmp = (1 << 12) | RT2860_MAX_AGG_SIZE;

	RAL_WRITE(sc, RT2860_REG_MAX_LEN_CFG, tmp);

	if (sc->mac_rev == 0x28720200) {
		/* set max. PSDU length from 16K to 32K bytes */

		tmp = RAL_READ(sc, RT2860_REG_MAX_LEN_CFG);

		tmp &= ~(3 << 12);
		tmp |= (2 << 12);

		RAL_WRITE(sc, RT2860_REG_MAX_LEN_CFG, tmp);
	}

	if (sc->mac_rev >= 0x28720200 && sc->mac_rev < 0x30700200) {
		tmp = RAL_READ(sc, RT2860_REG_MAX_LEN_CFG);

		tmp &= 0xfff;
		tmp |= 0x2000;

		RAL_WRITE(sc, RT2860_REG_MAX_LEN_CFG, tmp);
	}

	/* set mac address */

	rt2860_asic_set_macaddr(sc, IF_LLADDR(ifp));

	/* clear statistic registers */
	for (i = 0; i < (sizeof(uint32_t) * 6); i += sizeof(uint32_t))
		RAL_READ(sc, RT2860_REG_RX_STA_CNT0 + i);

	/* set RTS threshold */

	rt2860_asic_update_rtsthreshold(sc);

	/* set Tx power */

	rt2860_asic_update_txpower(sc);

	/* set up protection mode */

	sc->tx_ampdu_sessions = 0;

	rt2860_asic_updateprot(sc);

	/* clear beacon frame space (entries = 8, entry size = 512) */

	RAL_SET_REGION_4(sc, RT2860_REG_BEACON_BASE(0), 0, 1024);

	taskqueue_unblock(sc->taskqueue);

	/* init Tx rings (4 EDCAs + HCCA + MGMT) */

	for (i = 0; i < RT2860_SOFTC_TX_RING_COUNT; i++)
		rt2860_reset_tx_ring(sc, &sc->tx_ring[i]);

	for (i = 0; i < RT2860_SOFTC_TX_RING_COUNT; i++) {
		RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_BASE_PTR(i),
			sc->tx_ring[i].desc_phys_addr);
		RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_MAX_CNT(i),
			RT2860_SOFTC_TX_RING_DESC_COUNT);
		RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_CTX_IDX(i), 0);
	}

	/* init Rx ring */

	rt2860_reset_rx_ring(sc, &sc->rx_ring);

	RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_BASE_PTR,
		sc->rx_ring.desc_phys_addr);
	RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_MAX_CNT,
		RT2860_SOFTC_RX_RING_DATA_COUNT);
	RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_CALC_IDX,
		RT2860_SOFTC_RX_RING_DATA_COUNT - 1);

	/* wait while DMA engine is busy */

	for (ntries = 0; ntries < 100; ntries++) {
		tmp = RAL_READ(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG);
		if (!(tmp & (RT2860_REG_TX_DMA_BUSY | RT2860_REG_RX_DMA_BUSY)))
			break;

		DELAY(1000);
	}

	if (ntries == 100) {
		device_printf(sc->sc_dev, "timeout waiting for DMA engine\n");
		goto fail;
	}

	tmp &= 0xff0;
	tmp |= RT2860_REG_TX_WB_DDONE;

	RAL_WRITE(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG, tmp);

	/* disable interrupts mitigation */

	RAL_WRITE(sc, RT2860_REG_SCHDMA_DELAY_INT_CFG, 0);

	/* select Main antenna for 1T1R devices */
	if (sc->rf_rev == RT3070_RF_2020 ||
	    sc->rf_rev == RT3070_RF_3020 ||
	    sc->rf_rev == RT3070_RF_3320)
		rt3090_set_rx_antenna(sc, 0);

	/* send LEDs operating mode to microcontroller */
	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_LED1,
		RT2860_TOKEN_NO_INTR, sc->led_off[0]);
	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_LED2,
		RT2860_TOKEN_NO_INTR, sc->led_off[1]);
	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_LED3,
		RT2860_TOKEN_NO_INTR, sc->led_off[2]);

	/* turn radio LED on */

	rt2860_led_cmd(sc, RT2860_LED_CMD_RADIO_ON);

	/* write vendor-specific BBP values (from EEPROM) */
	for (i = 0; i < RT2860_SOFTC_BBP_EEPROM_COUNT; i++) {
		if (sc->bbp_eeprom[i].reg == 0x00 ||
			sc->bbp_eeprom[i].reg == 0xff)
			continue;

		rt2860_io_bbp_write(sc, sc->bbp_eeprom[i].reg,
			sc->bbp_eeprom[i].val);
	}

	if ((sc->mac_rev & 0xffff0000) >= 0x30710000)
		rt3090_rf_init(sc);

	if (sc->mac_rev != 0x28720200) {
		/* 0x28720200 don`t have RT2860_REG_SCHDMA_GPIO_CTRL_CFG */
		tmp = RAL_READ(sc, RT2860_REG_SCHDMA_GPIO_CTRL_CFG);
	        if (tmp & (1 << 2)) {
			rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_SLEEP,
				RT2860_TOKEN_RADIOOFF, 0x02ff);
			rt2860_io_mcu_cmd_check(sc, RT2860_TOKEN_RADIOOFF);

			rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_WAKEUP,
				RT2860_TOKEN_WAKEUP, 0);
			rt2860_io_mcu_cmd_check(sc, RT2860_TOKEN_WAKEUP);
		}
	}

	if ((sc->mac_rev & 0xffff0000) >= 0x30710000)
		rt3090_rf_wakeup(sc);

	/* disable non-existing Rx chains */

	tmp = rt2860_io_bbp_read(sc, 3);

	tmp &= ~((1 << 4) | (1 << 3));

	if (sc->nrxpath == 3)
		tmp |= (1 << 4);
	else if (sc->nrxpath == 2)
		tmp |= (1 << 3);

	rt2860_io_bbp_write(sc, 3, tmp);

	/* disable non-existing Tx chains */

	tmp = rt2860_io_bbp_read(sc, 1);

	tmp &= ~((1 << 4) | (1 << 3));

	if (sc->ntxpath == 2)
		tmp |= (1 << 4);

	rt2860_io_bbp_write(sc, 1, tmp);

	if ((sc->mac_rev & 0xffff0000) >= 0x30710000)
		rt3090_rf_setup(sc);

	if (sc->rf_rev == RT3070_RF_3022) {
		/* calibrate RF */
                tmp = rt2860_io_rf_read(sc, 30);
                tmp |= 0x80;
                rt2860_io_rf_write(sc, 30, tmp);
                DELAY(1000);
                tmp &= 0x7F;
                rt2860_io_rf_write(sc, 30, tmp);

                /* Initialize RF register to default value */
		rt2860_io_rf_load_defaults(sc);
	}

	/* set current channel */
	rt2860_rf_set_chan(sc, ic->ic_curchan);

	RAL_WRITE(sc, RT2860_WMM_TXOP0_CFG, 0);
	RAL_WRITE(sc, RT2860_WMM_TXOP1_CFG,
		(48 << 16) | 96);

	if ((sc->mac_rev & 0xffff) != 0x0101)
		RAL_WRITE(sc, RT2860_TXOP_CTRL_CFG, 0x583f);

	/* clear pending interrupts */

	RAL_WRITE(sc, RT2860_REG_SCHDMA_INT_STATUS, 0xffffffff);

	/* enable interrupts */

	tmp = RT2860_REG_INT_TX_COHERENT |
		RT2860_REG_INT_RX_COHERENT |
		RT2860_REG_INT_FIFO_STA_FULL |
		RT2860_REG_INT_TXRX_COHERENT |
		RT2860_REG_INT_TX_MGMT_DONE |
		RT2860_REG_INT_TX_HCCA_DONE |
		RT2860_REG_INT_TX_AC3_DONE |
		RT2860_REG_INT_TX_AC2_DONE |
		RT2860_REG_INT_TX_AC1_DONE |
		RT2860_REG_INT_TX_AC0_DONE |
		RT2860_REG_INT_RX_DONE;

	sc->intr_enable_mask = tmp;

	RAL_WRITE(sc, RT2860_REG_SCHDMA_INT_MASK, tmp);

	if (rt2860_txrx_enable(sc) != 0)
		goto fail;

	/* clear garbage interrupts */

	tmp = RAL_READ(sc, 0x1300);

	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
	ifp->if_drv_flags |= IFF_DRV_RUNNING;

	sc->periodic_round = 0;

	callout_reset(&sc->periodic_ch, hz / 10, rt2860_periodic, sc);

	return;

fail:

	rt2860_stop_locked(sc);
#undef nitems
}

/*
 * rt2860_init
 */
static void
rt2860_init(void *priv)
{
	struct rt2860_softc *sc;

	sc = priv;

	RT2860_SOFTC_LOCK(sc);

	rt2860_init_locked(sc);

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_init_bbp
 */
static int
rt2860_init_bbp(struct rt2860_softc *sc)
{
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
	int ntries, i;
	uint8_t tmp;

	for (ntries = 0; ntries < 20; ntries++) {
		tmp = rt2860_io_bbp_read(sc, 0);
		if (tmp != 0x00 && tmp != 0xff)
			break;
	}

	if (tmp == 0x00 || tmp == 0xff) {
		device_printf(sc->sc_dev,
		    "timeout waiting for BBP to wakeup\n");
		return ETIMEDOUT;
	}

	for (i = 0; i < nitems(rt2860_def_bbp); i++) {
		rt2860_io_bbp_write(sc, rt2860_def_bbp[i].reg,
			rt2860_def_bbp[i].val);
	}

	if ((sc->mac_rev & 0xffff) != 0x0101)
		rt2860_io_bbp_write(sc, 84, 0x19);

	if (sc->mac_rev == 0x28600100) {
		rt2860_io_bbp_write(sc, 69, 0x16);
		rt2860_io_bbp_write(sc, 73, 0x12);
	}

	return 0;
#undef nitems
}

/*
 * rt2860_stop_locked
 */
static void
rt2860_stop_locked(void *priv)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	struct ieee80211com *ic;
	uint32_t tmp;

	sc = priv;
	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	RT2860_DPRINTF(sc, RT2860_DEBUG_ANY, "stopping\n");

	RT2860_SOFTC_ASSERT_LOCKED(sc);

	sc->tx_timer = 0;

	if (ifp->if_drv_flags & IFF_DRV_RUNNING)
		rt2860_led_cmd(sc, RT2860_LED_CMD_RADIO_OFF);

	ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);

	callout_stop(&sc->periodic_ch);
	callout_stop(&sc->tx_watchdog_ch);

	RT2860_SOFTC_UNLOCK(sc);

	taskqueue_block(sc->taskqueue);

	taskqueue_drain(sc->taskqueue, &sc->rx_done_task);
	taskqueue_drain(sc->taskqueue, &sc->tx_done_task);
	taskqueue_drain(sc->taskqueue, &sc->fifo_sta_full_task);
	taskqueue_drain(sc->taskqueue, &sc->periodic_task);

	RT2860_SOFTC_LOCK(sc);

	/* clear key tables */

	rt2860_asic_clear_keytables(sc);

	/* disable interrupts */

	RAL_WRITE(sc, RT2860_REG_SCHDMA_INT_MASK, 0);

	/* disable Tx/Rx */

	tmp = RAL_READ(sc, RT2860_MAC_SYS_CTRL);

	tmp &= ~(RT2860_REG_RX_ENABLE | RT2860_REG_TX_ENABLE);

	RAL_WRITE(sc, RT2860_MAC_SYS_CTRL, tmp);

	/* reset adapter */

	RAL_WRITE(sc, RT2860_MAC_SYS_CTRL,
		RT2860_REG_MAC_SRST | RT2860_REG_BBP_HRST);
	RAL_WRITE(sc, RT2860_MAC_SYS_CTRL, 0);
}

/*
 * rt2860_stop
 */
static void
rt2860_stop(void *priv)
{
	struct rt2860_softc *sc;

	sc = priv;

	RT2860_SOFTC_LOCK(sc);

	rt2860_stop_locked(sc);

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_start
 */
static void
rt2860_start(struct ifnet *ifp)
{
	struct rt2860_softc *sc;
#ifdef RT2860_NO_HW_CRYPTO
	struct ieee80211_frame *wh;
	struct ieee80211_key *k;
#endif
	struct ieee80211_node *ni;
	struct mbuf *m;
	int qid;

	sc = ifp->if_softc;

	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING) || sc->sc_invalid)
		return;

	RT2860_SOFTC_LOCK(sc);

	for (;;) {
		IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
		if (m == NULL)
			break;

		ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;

#ifdef RT2860_NO_HW_CRYPTO
		KASSERT(ni != NULL, ("%s:%d: fail", __func__, __LINE__));
		wh = mtod(m, struct ieee80211_frame *);
		if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
			k = ieee80211_crypto_encap(ni, m);
			if (k == NULL) {
				ieee80211_free_node(ni);
				m_freem(m);
				ifp->if_oerrors++;
				continue;
			}
		}
		wh = NULL;	/* Catch any invalid use */
#endif

		m->m_pkthdr.rcvif = NULL;

		qid = M_WME_GETAC(m);

		RT2860_SOFTC_TX_RING_LOCK(&sc->tx_ring[qid]);

		if (sc->tx_ring[qid].data_queued >=
		    RT2860_SOFTC_TX_RING_DATA_COUNT) {
			RT2860_SOFTC_TX_RING_UNLOCK(&sc->tx_ring[qid]);

			RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
			    "if_start: Tx ring with qid=%d is full\n", qid);

			m_freem(m);
			ieee80211_free_node(ni);

			ifp->if_drv_flags |= IFF_DRV_OACTIVE;
			ifp->if_oerrors++;

			sc->tx_data_queue_full[qid]++;

			break;
		}

		if (rt2860_tx_data(sc, m, ni, qid) != 0) {
			RT2860_SOFTC_TX_RING_UNLOCK(&sc->tx_ring[qid]);

			ieee80211_free_node(ni);

			ifp->if_oerrors++;

			break;
		}

		RT2860_SOFTC_TX_RING_UNLOCK(&sc->tx_ring[qid]);

		rt2860_drain_fifo_stats(sc);

		sc->tx_timer = RT2860_TX_WATCHDOG_TIMEOUT;

		callout_reset(&sc->tx_watchdog_ch, hz, rt2860_tx_watchdog, sc);
	}
	RT2860_SOFTC_UNLOCK(sc);

}

/*
 * rt2860_ioctl
 */
static int
rt2860_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifreq *ifr;
	int error, startall;

	sc = ifp->if_softc;
	ic = ifp->if_l2com;
	ifr = (struct ifreq *) data;

	error = 0;

	switch (cmd) {
	case SIOCSIFFLAGS:
		startall = 0;

		RT2860_SOFTC_LOCK(sc);

		if (ifp->if_flags & IFF_UP) {
			if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
				if ((ifp->if_flags ^ sc->if_flags) & IFF_PROMISC)
					rt2860_asic_update_promisc(sc);
			} else {
				rt2860_init_locked(sc);
				startall = 1;
			}
		} else {
			if (ifp->if_drv_flags & IFF_DRV_RUNNING)
				rt2860_stop_locked(sc);
		}

		sc->if_flags = ifp->if_flags;

		RT2860_SOFTC_UNLOCK(sc);

		if (startall)
			ieee80211_start_all(ic);
		break;

	case SIOCGIFMEDIA:
	case SIOCSIFMEDIA:
		error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd);
		break;

	case SIOCGIFADDR:
		error = ether_ioctl(ifp, cmd, data);
		break;

	default:
		error = EINVAL;
		break;
	}

	return error;
}

/*
 * rt2860_vap_create
 */
static struct ieee80211vap *
rt2860_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ],
    int unit, enum ieee80211_opmode opmode, int flags,
    const uint8_t bssid[IEEE80211_ADDR_LEN],
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	struct rt2860_softc_vap *rvap;
	struct ieee80211vap *vap;

	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE, "VAP create: opmode=%s\n",
	    ieee80211_opmode_name[opmode]);

	switch (opmode) {
	case IEEE80211_M_IBSS:
	case IEEE80211_M_STA:
	case IEEE80211_M_AHDEMO:
	case IEEE80211_M_HOSTAP:
	case IEEE80211_M_MBSS:
		if ((sc->napvaps + sc->nadhocvaps + sc->nstavaps) != 0) {
			if_printf(ifp, "only 1 VAP supported\n");
			return NULL;
		}

		if (opmode == IEEE80211_M_STA)
			flags |= IEEE80211_CLONE_NOBEACONS;
		break;

	case IEEE80211_M_WDS:
		if (sc->napvaps == 0) {
			if_printf(ifp, "WDS only supported in AP mode\n");
			return NULL;
		}
		break;

	case IEEE80211_M_MONITOR:
		break;

	default:
		if_printf(ifp, "unknown opmode %d\n", opmode);
		return NULL;
	}

	rvap = (struct rt2860_softc_vap *) malloc(sizeof(struct rt2860_softc_vap),
		M_80211_VAP, M_NOWAIT | M_ZERO);
	if (rvap == NULL)
		return NULL;

	vap = &rvap->vap;

	ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid, mac);

	rvap->newstate = vap->iv_newstate;
	vap->iv_newstate = rt2860_vap_newstate;

	vap->iv_reset = rt2860_vap_reset;
#ifndef RT2860_NO_HW_CRYPTO
	vap->iv_key_update_begin = rt2860_vap_key_update_begin;
	vap->iv_key_update_end = rt2860_vap_key_update_end;
	vap->iv_key_set = rt2860_vap_key_set;
	vap->iv_key_delete = rt2860_vap_key_delete;
#endif
	vap->iv_update_beacon = rt2860_vap_update_beacon;

	rt2860_amrr_init(&rvap->amrr, vap,
		sc->ntxpath,
		RT2860_AMRR_MIN_SUCCESS_THRESHOLD,
		RT2860_AMRR_MAX_SUCCESS_THRESHOLD,
		500);

	vap->iv_max_aid = RT2860_SOFTC_STAID_COUNT;

	/* overwrite default Rx A-MPDU factor */

	vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K;
	vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_NA;
	vap->iv_ampdu_limit = vap->iv_ampdu_rxmax;

	ieee80211_vap_attach(vap, rt2860_media_change, ieee80211_media_status);

	switch (vap->iv_opmode) {
	case IEEE80211_M_HOSTAP:
	case IEEE80211_M_MBSS:
	case IEEE80211_M_AHDEMO:
		sc->napvaps++;
		break;

	case IEEE80211_M_IBSS:
		sc->nadhocvaps++;
		break;

	case IEEE80211_M_STA:
		sc->nstavaps++;
		break;

	case IEEE80211_M_WDS:
		sc->nwdsvaps++;
		break;

	default:
		break;
	}

	sc->nvaps++;

	if (sc->napvaps > 0)
		ic->ic_opmode = IEEE80211_M_HOSTAP;
	else if (sc->nadhocvaps > 0)
		ic->ic_opmode = IEEE80211_M_IBSS;
	else if (sc->nstavaps > 0)
		ic->ic_opmode = IEEE80211_M_STA;
	else
		ic->ic_opmode = opmode;

	return vap;
}

/*
 * rt2860_vap_delete
 */
static void
rt2860_vap_delete(struct ieee80211vap *vap)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_vap *rvap;
	enum ieee80211_opmode opmode;

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rvap = (struct rt2860_softc_vap *) vap;
	opmode = vap->iv_opmode;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE, "VAP delete: opmode=%s\n",
	    ieee80211_opmode_name[opmode]);

	rt2860_amrr_cleanup(&rvap->amrr);

	ieee80211_vap_detach(vap);

	if (rvap->beacon_mbuf != NULL) {
		m_free(rvap->beacon_mbuf);
		rvap->beacon_mbuf = NULL;
	}

	switch (opmode) {
	case IEEE80211_M_HOSTAP:
	case IEEE80211_M_MBSS:
	case IEEE80211_M_AHDEMO:
		sc->napvaps--;
		break;
	case IEEE80211_M_IBSS:
		sc->nadhocvaps--;
		break;
	case IEEE80211_M_STA:
		sc->nstavaps--;
		break;
	case IEEE80211_M_WDS:
		sc->nwdsvaps--;
		break;
	default:
		break;
	}

	sc->nvaps--;

	if (sc->napvaps > 0)
		ic->ic_opmode = IEEE80211_M_HOSTAP;
	else if (sc->nadhocvaps > 0)
		ic->ic_opmode = IEEE80211_M_IBSS;
	else if (sc->nstavaps > 0)
		ic->ic_opmode = IEEE80211_M_STA;

	free(rvap, M_80211_VAP);
}

/*
 * rt2860_reset_vap
 */
static int
rt2860_vap_reset(struct ieee80211vap *vap, u_long cmd)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_vap *rvap;
	int error;

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rvap = (struct rt2860_softc_vap *) vap;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE, "VAP reset: cmd=%lu\n", cmd);

	error = 0;

	RT2860_SOFTC_LOCK(sc);

	switch (cmd){
	case IEEE80211_IOC_RTSTHRESHOLD:
	case IEEE80211_IOC_AMSDU:
		rt2860_asic_update_rtsthreshold(sc);
		break;
	case IEEE80211_IOC_PROTMODE:
	case IEEE80211_IOC_HTPROTMODE:
		rt2860_asic_updateprot(sc);
		break;
	case IEEE80211_IOC_TXPOWER:
		rt2860_asic_update_txpower(sc);
		break;
	case IEEE80211_IOC_BURST:
		rt2860_asic_updateslot(sc);
		break;
	case IEEE80211_IOC_SHORTGI:
	case IEEE80211_IOC_AMPDU_DENSITY:
	case IEEE80211_IOC_SMPS:
		break;
	default:
		error = ENETRESET;
		break;
	}

	RT2860_SOFTC_UNLOCK(sc);

	return error;
}

/*
 * rt2860_vap_newstate
 */
static int
rt2860_vap_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate,
    int arg)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_vap *rvap;
	struct ieee80211_node *ni;
	enum ieee80211_state ostate;
	int error;

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rvap = (struct rt2860_softc_vap *) vap;

	ostate = vap->iv_state;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE, "VAP newstate: %s -> %s\n",
	    ieee80211_state_name[ostate], ieee80211_state_name[nstate]);

	error = rvap->newstate(vap, nstate, arg);
	if (error != 0)
		return error;

	IEEE80211_UNLOCK(ic);

	RT2860_SOFTC_LOCK(sc);

	/* turn link LED off */

	if (nstate != IEEE80211_S_RUN)
		rt2860_led_cmd(sc, RT2860_LED_CMD_RADIO_OFF);

	switch (nstate) {
	case IEEE80211_S_INIT:
		rt2860_asic_disable_tsf_sync(sc);
		break;
	case IEEE80211_S_RUN:
		ni = vap->iv_bss;

		rt2860_rf_set_chan(sc, ni->ni_chan);

		if (vap->iv_opmode != IEEE80211_M_MONITOR) {
			rt2860_asic_enable_mrr(sc);
			rt2860_asic_set_txpreamble(sc);
			rt2860_asic_set_basicrates(sc);
			rt2860_asic_update_txpower(sc);
			rt2860_asic_set_bssid(sc, ni->ni_bssid);
		}

		if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
	    	    vap->iv_opmode == IEEE80211_M_IBSS ||
	    	    vap->iv_opmode == IEEE80211_M_MBSS) {
			error = rt2860_beacon_alloc(sc, vap);
			if (error != 0)
				break;
		}

		if (vap->iv_opmode != IEEE80211_M_MONITOR)
			rt2860_asic_enable_tsf_sync(sc);

		/* turn link LED on */
		if (vap->iv_opmode != IEEE80211_M_MONITOR) {
			rt2860_led_cmd(sc, RT2860_LED_CMD_RADIO_ON |
	    		(IEEE80211_IS_CHAN_2GHZ(ni->ni_chan) ?
	     			RT2860_LED_CMD_LINK_2GHZ : RT2860_LED_CMD_LINK_5GHZ));
		}
		break;
	case IEEE80211_S_SLEEP:
		break;
	default:
		break;
	}

	RT2860_SOFTC_UNLOCK(sc);

	IEEE80211_LOCK(ic);

	return error;
}

#ifndef RT2860_NO_HW_CRYPTO
/*
 * rt2860_vap_key_update_begin
 */
static void
rt2860_vap_key_update_begin(struct ieee80211vap *vap)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_KEY, "VAP key update begin\n");

	taskqueue_block(sc->taskqueue);

	IF_LOCK(&ifp->if_snd);
}

/*
 * rt2860_vap_key_update_end
 */
static void
rt2860_vap_key_update_end(struct ieee80211vap *vap)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_KEY, "VAP key update end\n");

	IF_UNLOCK(&ifp->if_snd);

	taskqueue_unblock(sc->taskqueue);
}

/*
 * rt2860_vap_key_set
 */
static int
rt2860_vap_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k,
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct ieee80211_node *ni;
	struct rt2860_softc_node *rni;
	uint16_t key_base, keymode_base;
	uint8_t mode, vapid, wcid, iv[8];
	uint32_t tmp;

	switch (k->wk_cipher->ic_cipher) {
	case IEEE80211_CIPHER_WEP:
		if(k->wk_keylen < 8)
			mode = RT2860_MODE_WEP40;
		else
			mode = RT2860_MODE_WEP104;
		break;
	case IEEE80211_CIPHER_TKIP:
		mode = RT2860_MODE_TKIP;
		break;
	case IEEE80211_CIPHER_AES_CCM:
		mode = RT2860_MODE_AES_CCMP;
		break;
	default:
		printf("Wrong key CIPHER (%d)\n", k->wk_cipher->ic_cipher);
		return 0;
	}

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_KEY,
	    "VAP key set: keyix=%d, keylen=%d, macaddr=%s, mode=%d, group=%d\n",
	    k->wk_keyix, k->wk_keylen, ether_sprintf(k->wk_macaddr),
	    mode, (k->wk_flags & IEEE80211_KEY_GROUP) ? 1 : 0);

	if (!(k->wk_flags & IEEE80211_KEY_GROUP)) {
		/* install pairwise key */

		if (vap->iv_opmode == IEEE80211_M_HOSTAP)
			ni = ieee80211_find_vap_node(&ic->ic_sta, vap, mac);
		else
			ni = vap->iv_bss;

		rni = (struct rt2860_softc_node *) ni;

		vapid = 0;
		wcid = (ni != NULL) ? rni->staid : 0;
		key_base = RT2860_REG_PKEY(wcid);

		if (ni != NULL)
			ieee80211_free_node(ni);

		if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_WEP) {
			memset(iv, 0, 8);

			iv[3] = (k->wk_keyix << 6);
		} else {
			if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP) {
				iv[0] = (k->wk_keytsc >> 8);
				iv[1] = ((iv[0] | 0x20) & 0x7f);
				iv[2] = k->wk_keytsc;
			} else {
				/* AES CCMP */
				iv[0] = k->wk_keytsc;
				iv[1] = k->wk_keytsc >> 8;
				iv[2] = 0;
			}

			iv[3] = ((k->wk_keyix << 6) | IEEE80211_WEP_EXTIV);
			iv[4] = (k->wk_keytsc >> 16);
			iv[5] = (k->wk_keytsc >> 24);
			iv[6] = (k->wk_keytsc >> 32);
			iv[7] = (k->wk_keytsc >> 40);

			RT2860_DPRINTF(sc, RT2860_DEBUG_KEY,
			    "VAP key set: iv=%02x %02x %02x %02x %02x %02x %02x %02x\n",
			    iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7]);
		}

		RAL_WRITE_REGION_1(sc, RT2860_REG_IVEIV(wcid), iv, 8);

		if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP) {
			RAL_WRITE_REGION_1(sc, key_base, k->wk_key, 16);

			if (vap->iv_opmode != IEEE80211_M_HOSTAP) {
				RAL_WRITE_REGION_1(sc, key_base + 16, &k->wk_key[16], 8);
				RAL_WRITE_REGION_1(sc, key_base + 24, &k->wk_key[24], 8);
			} else {
				RAL_WRITE_REGION_1(sc, key_base + 16, &k->wk_key[24], 8);
				RAL_WRITE_REGION_1(sc, key_base + 24, &k->wk_key[16], 8);
			}
		} else {
			RAL_WRITE_REGION_1(sc, key_base, k->wk_key, k->wk_keylen);
		}
		tmp = (vapid << RT2860_VAP_SHIFT) |
			(mode << RT2860_MODE_SHIFT) | RT2860_RX_PKEY_EN;

		RAL_WRITE(sc, RT2860_REG_WCID_ATTR(wcid), tmp);
	}

	if ((k->wk_flags & IEEE80211_KEY_GROUP) ||
		(k->wk_cipher->ic_cipher == IEEE80211_CIPHER_WEP)) {
		/* install group key */

		vapid = 0;
		wcid = RT2860_WCID_MCAST;
		key_base = RT2860_REG_SKEY(vapid, k->wk_keyix);
		keymode_base = RT2860_REG_SKEY_MODE(vapid);

		if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP) {
			RAL_WRITE_REGION_1(sc, key_base, k->wk_key, 16);

			if (vap->iv_opmode != IEEE80211_M_HOSTAP) {
				RAL_WRITE_REGION_1(sc, key_base + 16,
				    &k->wk_key[16], 8);
				RAL_WRITE_REGION_1(sc, key_base + 24,
				    &k->wk_key[24], 8);
			} else {
				RAL_WRITE_REGION_1(sc, key_base + 16,
				    &k->wk_key[24], 8);
				RAL_WRITE_REGION_1(sc, key_base + 24,
				    &k->wk_key[16], 8);
			}
		} else {
			RAL_WRITE_REGION_1(sc, key_base, k->wk_key, k->wk_keylen);
		}

		tmp = RAL_READ(sc, keymode_base);

		tmp &= ~(0xf << (k->wk_keyix * 4 + 16 * (vapid % 2)));
		tmp |= (mode << (k->wk_keyix * 4 + 16 * (vapid % 2)));

		RAL_WRITE(sc, keymode_base, tmp);

		if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
			if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_WEP) {
				memset(iv, 0, 8);

				iv[3] = (k->wk_keyix << 6);
			} else {
				if (k->wk_cipher->ic_cipher ==
				    IEEE80211_CIPHER_TKIP) {
					iv[0] = (k->wk_keytsc >> 8);
					iv[1] = ((iv[0] | 0x20) & 0x7f);
					iv[2] = k->wk_keytsc;
				} else {
					/* AES CCMP */

					iv[0] = k->wk_keytsc;
					iv[1] = k->wk_keytsc >> 8;
					iv[2] = 0;
				}

				iv[3] = ((k->wk_keyix << 6) |
				    IEEE80211_WEP_EXTIV);
				iv[4] = (k->wk_keytsc >> 16);
				iv[5] = (k->wk_keytsc >> 24);
				iv[6] = (k->wk_keytsc >> 32);
				iv[7] = (k->wk_keytsc >> 40);

				RT2860_DPRINTF(sc, RT2860_DEBUG_KEY,
				    "VAP key set: iv=%02x %02x %02x %02x "
				    "%02x %02x %02x %02x\n",
				    iv[0], iv[1], iv[2], iv[3],
				    iv[4], iv[5], iv[6], iv[7]);
			}

			RAL_WRITE_REGION_1(sc, RT2860_REG_IVEIV(wcid), iv, 8);

			tmp = (vapid << RT2860_VAP_SHIFT) |
				(mode << RT2860_MODE_SHIFT);

			RAL_WRITE(sc, RT2860_REG_WCID_ATTR(wcid), tmp);
		}
	}

	return 1;
}

/*
 * rt2860_vap_key_delete
 */
static int
rt2860_vap_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	uint8_t vapid, wcid;
	uint32_t tmp;

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_KEY,
		"VAP key delete: keyix=%d, keylen=%d, macaddr=%s, group=%d\n",
		k->wk_keyix, k->wk_keylen, ether_sprintf(k->wk_macaddr),
		(k->wk_flags & IEEE80211_KEY_GROUP) ? 1 : 0);

	if (k->wk_flags & IEEE80211_KEY_GROUP) {
		/* remove group key */

		vapid = 0;
		wcid = RT2860_WCID_MCAST;

		tmp = RAL_READ(sc, RT2860_REG_SKEY_MODE(vapid));

		tmp &= ~(0xf << (k->wk_keyix * 4 + 16 * (vapid % 2)));
		tmp |= (RT2860_MODE_NOSEC << (k->wk_keyix * 4 + 16 * (vapid % 2)));

		RAL_WRITE(sc, RT2860_REG_SKEY_MODE(vapid), tmp);

		if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
			tmp = (vapid << RT2860_VAP_SHIFT) |
				RT2860_RX_PKEY_EN;

			RAL_WRITE(sc, RT2860_REG_WCID_ATTR(wcid), tmp);
		}
	}

	return 1;
}
#endif

/*
 * rt2860_vap_update_beacon
 */
static void
rt2860_vap_update_beacon(struct ieee80211vap *vap, int what)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_vap *rvap;
	struct mbuf *m;
	struct ieee80211_beacon_offsets *bo;
	int error;

	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rvap = (struct rt2860_softc_vap *) vap;
	m = rvap->beacon_mbuf;
	bo = &rvap->beacon_offsets;

	RT2860_DPRINTF(sc, RT2860_DEBUG_BEACON,
	    "VAP update beacon: what=%d\n", what);

	setbit(bo->bo_flags, what);

	if (m == NULL) {
		error = rt2860_beacon_alloc(sc, vap);
		if (error != 0)
			return;

		m = rvap->beacon_mbuf;
	}

	ieee80211_beacon_update(vap->iv_bss, bo, m, 0);
}

/*
 * rt2860_media_change
 */
static int
rt2860_media_change(struct ifnet *ifp)
{
	int error;

	error = ieee80211_media_change(ifp);

	return (error == ENETRESET ? 0 : error);
}

/*
 * rt2860_node_alloc
 */
static struct ieee80211_node *
rt2860_node_alloc(struct ieee80211vap *vap,
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	return malloc(sizeof(struct rt2860_softc_node),
		M_80211_NODE, M_NOWAIT | M_ZERO);
}

/*
 * rt2860_node_cleanup
 */
static void
rt2860_node_cleanup(struct ieee80211_node *ni)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_node *rni;
	uint8_t vapid, wcid;
	uint32_t tmp;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rni = (struct rt2860_softc_node *) ni;

	RT2860_DPRINTF(sc, RT2860_DEBUG_NODE,
	    "node cleanup: macaddr=%s, associd=0x%04x, staid=0x%02x\n",
	    ether_sprintf(ni->ni_macaddr), ni->ni_associd, rni->staid);

	if (rni->staid != 0) {
		vapid = 0;
		wcid = rni->staid;

		tmp = (vapid << RT2860_VAP_SHIFT) |
			RT2860_RX_PKEY_EN;

		RAL_WRITE(sc, RT2860_REG_WCID_ATTR(wcid), tmp);

		RAL_WRITE(sc, RT2860_REG_WCID(wcid), 0x00000000);
		RAL_WRITE(sc, RT2860_REG_WCID(wcid) + 4, 0x00000000);

		rt2860_staid_delete(sc, rni->staid);

		rni->staid = 0;
	}

	sc->node_cleanup(ni);
}

/*
 * rt2860_node_getmimoinfo
 */
static void
rt2860_node_getmimoinfo(const struct ieee80211_node *ni,
    struct ieee80211_mimo_info *mi)
{
	const struct rt2860_softc_node *rni;
	int i;

	rni = (const struct rt2860_softc_node *) ni;

	for (i = 0; i < RT2860_SOFTC_RSSI_COUNT; i++) {
		mi->rssi[i] = rni->last_rssi_dbm[i];
		mi->noise[i] = RT2860_NOISE_FLOOR;
	}
}

/*
 * rt2860_setregdomain
 */
static int
rt2860_setregdomain(struct ieee80211com *ic, struct ieee80211_regdomain *reg,
    int nchans, struct ieee80211_channel chans[])
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;

	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE,
	    "set regulatory domain: country=%d, country code string=%c%c, "
	    "location=%c\n",
	    reg->country, reg->isocc[0], reg->isocc[1], reg->location);

	return 0;
}

/*
 * rt2860_getradiocaps
 */
static void
rt2860_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans,
    struct ieee80211_channel chans[])
{
	*nchans = (ic->ic_nchans >= maxchans) ? maxchans : ic->ic_nchans;

	memcpy(chans, ic->ic_channels, (*nchans) * sizeof(struct ieee80211_channel));
}

/*
 * rt2860_scan_start
 */
static void
rt2860_scan_start(struct ieee80211com *ic)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;

	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_SOFTC_LOCK(sc);

	rt2860_asic_disable_tsf_sync(sc);

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_scan_end
 */
static void
rt2860_scan_end(struct ieee80211com *ic)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;

	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_SOFTC_LOCK(sc);

	rt2860_asic_enable_tsf_sync(sc);

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_set_channel
 */
static void
rt2860_set_channel(struct ieee80211com *ic)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;

	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	RT2860_DPRINTF(sc, RT2860_DEBUG_CHAN,
		"set channel: channel=%u, HT%s%s\n",
		ieee80211_chan2ieee(ic, ic->ic_curchan),
		!IEEE80211_IS_CHAN_HT(ic->ic_curchan) ? " disabled" :
		    IEEE80211_IS_CHAN_HT20(ic->ic_curchan) ? "20":
			IEEE80211_IS_CHAN_HT40U(ic->ic_curchan) ?
			    "40U" : "40D",
		(ic->ic_flags & IEEE80211_F_SCAN) ? ", scanning" : "");

	RT2860_SOFTC_LOCK(sc);

	rt2860_rf_set_chan(sc, ic->ic_curchan);

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_newassoc
 */
static void
rt2860_newassoc(struct ieee80211_node *ni, int isnew)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct ieee80211vap *vap;
	struct rt2860_softc_vap *rvap;
	struct rt2860_softc_node *rni;
	uint16_t aid;
	uint8_t wcid;
	uint32_t tmp;

	vap = ni->ni_vap;
	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rvap = (struct rt2860_softc_vap *) vap;
	rni = (struct rt2860_softc_node *) ni;

	if (isnew) {
		aid = IEEE80211_AID(ni->ni_associd);
		rni->staid = rt2860_staid_alloc(sc, aid);
		wcid = rni->staid;

		tmp = (ni->ni_macaddr[3] << 24) |
			(ni->ni_macaddr[2] << 16) |
			(ni->ni_macaddr[1] << 8) |
			ni->ni_macaddr[0];

		RAL_WRITE(sc, RT2860_REG_WCID(wcid), tmp);

		tmp = (ni->ni_macaddr[5] << 8) |
			ni->ni_macaddr[4];

		RAL_WRITE(sc, RT2860_REG_WCID(wcid) + 4, tmp);

		rt2860_amrr_node_init(&rvap->amrr, &sc->amrr_node[wcid], ni);

		RT2860_DPRINTF(sc, RT2860_DEBUG_RATE,
		    "initial%s node Tx rate: associd=0x%04x, rate=0x%02x, "
		    "max rate=0x%02x\n",
		    (ni->ni_flags & IEEE80211_NODE_HT) ? " HT" : "",
		    ni->ni_associd, ni->ni_txrate,
		    (ni->ni_flags & IEEE80211_NODE_HT) ?
		    (ni->ni_htrates.rs_rates[ni->ni_htrates.rs_nrates - 1] |
			IEEE80211_RATE_MCS) :
		    (ni->ni_rates.rs_rates[ni->ni_rates.rs_nrates - 1] &
			IEEE80211_RATE_VAL));

		rt2860_asic_updateprot(sc);
		rt2860_asic_updateslot(sc);
		rt2860_asic_set_txpreamble(sc);
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_NODE,
	    "new association: isnew=%d, macaddr=%s, associd=0x%04x, "
	    "staid=0x%02x, QoS %s, ERP %s, HT %s\n",
	    isnew, ether_sprintf(ni->ni_macaddr),
	    ni->ni_associd, rni->staid,
	    (ni->ni_flags & IEEE80211_NODE_QOS) ? "enabled" : "disabled",
	    (ni->ni_flags & IEEE80211_NODE_ERP) ? "enabled" : "disabled",
	    (ni->ni_flags & IEEE80211_NODE_HT) ? "enabled" : "disabled");
}

/*
 * rt2860_updateslot
 */
static void
rt2860_updateslot(struct ifnet *ifp)
{
	struct rt2860_softc *sc;

	sc = ifp->if_softc;

	rt2860_asic_updateslot(sc);
}

/*
 * rt2860_update_promisc
 */
static void
rt2860_update_promisc(struct ifnet *ifp)
{
	struct rt2860_softc *sc;

	sc = ifp->if_softc;

	RT2860_SOFTC_LOCK(sc);
	rt2860_asic_update_promisc(sc);
	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_update_mcast
 */
static void
rt2860_update_mcast(struct ifnet *ifp)
{
	struct rt2860_softc *sc;

	sc = ifp->if_softc;
}

/*
 * rt2860_wme_update
 */
static int
rt2860_wme_update(struct ieee80211com *ic)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;

	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	rt2860_asic_wme_update(sc);

	return 0;
}

/*
 * rt2860_raw_xmit
 */
static int
rt2860_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
    const struct ieee80211_bpf_params *params)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
#ifdef notyet
	struct ieee80211_frame *wh;
	int ismcast;
#endif

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;

	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
		m_freem(m);
		ieee80211_free_node(ni);

		return ENETDOWN;
	}

	RT2860_SOFTC_TX_RING_LOCK(&sc->tx_ring[sc->tx_ring_mgtqid]);
	if (sc->tx_ring[sc->tx_ring_mgtqid].data_queued >=
	    RT2860_SOFTC_TX_RING_DATA_COUNT) {
		RT2860_SOFTC_TX_RING_UNLOCK(&sc->tx_ring[sc->tx_ring_mgtqid]);

		RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
			"raw xmit: Tx ring with qid=%d is full\n",
			sc->tx_ring_mgtqid);

		ifp->if_drv_flags |= IFF_DRV_OACTIVE;

		m_freem(m);
		ieee80211_free_node(ni);

		sc->tx_data_queue_full[sc->tx_ring_mgtqid]++;

		return ENOBUFS;
	}

#ifdef notyet
	if (params != NULL) {
		if (params->ibp_flags & IEEE80211_BPF_CRYPTO) {
			/* TODO */
		}
		if (params->ibp_flags & IEEE80211_BPF_RTS) {
			/* TODO */
		} else if (params->ibp_flags & IEEE80211_BPF_CTS) {
			/* TODO */
		}
		wh = mtod(m, struct ieee80211_frame *);
		ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
		if ((params->ibp_flags & IEEE80211_BPF_NOACK) || ismcast) {
			/* TODO */
		}
		/* Apply rate index params->ibp_rate0 */
		if (params->ibp_flags & IEEE80211_BPF_SHORTPRE) {
			/* TODO handle Short Preamble */
		}
		/*
		 * try0 = params->ibp_try0;
		 * ismrr = (params->ibp_try1 != 0);
		 * txantenna = params->ibp_pri >> 2;
		 * params->ibp_ctsrate;
		 * pri = params->ibp_pri & 3;
		 * txpower = params->ibp_power;
		 * if (ismrr) {
		 * params->ibp_rate1
		 * params->ibp_try1
		 * params->ibp_rate2
		 * params->ibp_try2
		 * params->ibp_rate3
		 * params->ibp_try3
		 * }
		 * Everything stolen from ath(9) driver
		 */

	}
#endif

	if (rt2860_tx_mgmt(sc, m, ni, sc->tx_ring_mgtqid) != 0) {
		RT2860_SOFTC_TX_RING_UNLOCK(&sc->tx_ring[sc->tx_ring_mgtqid]);

		ifp->if_oerrors++;

		ieee80211_free_node(ni);

		return EIO;
	}

	RT2860_SOFTC_TX_RING_UNLOCK(&sc->tx_ring[sc->tx_ring_mgtqid]);

	sc->tx_timer = RT2860_TX_WATCHDOG_TIMEOUT;

	return 0;
}

/*
 * rt2860_recv_action
 */
static int
rt2860_recv_action(struct ieee80211_node *ni,
    const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_node *rni;
	const struct ieee80211_action *ia;
	uint16_t baparamset;
	uint8_t wcid;
	int tid;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rni = (struct rt2860_softc_node *) ni;

	ia = (const struct ieee80211_action *) frm;

	if (ia->ia_category == IEEE80211_ACTION_CAT_BA) {
		switch (ia->ia_action) {
		/* IEEE80211_ACTION_BA_DELBA */
		case IEEE80211_ACTION_BA_DELBA:
			baparamset = LE_READ_2(frm + 2);
			tid = RT2860_MS(baparamset, IEEE80211_BAPS_TID);
			wcid = rni->staid;

			RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
			    "received DELBA request: associd=0x%04x, "
			    "staid=0x%02x, tid=%d\n",
			    ni->ni_associd, rni->staid, tid);

			if (rni->staid != 0) {
				RT2860_SOFTC_LOCK(sc);

				rt2860_asic_del_ba_session(sc, wcid, tid);
				RT2860_SOFTC_UNLOCK(sc);

			}
			break;
		}
	}

	return sc->recv_action(ni, wh, frm, efrm);
}

/*
 * rt2860_send_action
 */
static int
rt2860_send_action(struct ieee80211_node *ni, int cat, int act, void *sa)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_node *rni;
	uint16_t *args, status, baparamset;
	uint8_t wcid;
	int tid, bufsize;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rni = (struct rt2860_softc_node *) ni;

	wcid = rni->staid;
	args = sa;

	if (cat == IEEE80211_ACTION_CAT_BA) {
		switch (act) {
		/* IEEE80211_ACTION_BA_ADDBA_RESPONSE */
		case IEEE80211_ACTION_BA_ADDBA_RESPONSE:
			status = args[1];
			baparamset = args[2];
			tid = RT2860_MS(baparamset, IEEE80211_BAPS_TID);
			bufsize = RT2860_MS(baparamset, IEEE80211_BAPS_BUFSIZ);

			RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
			    "sending ADDBA response: associd=0x%04x, "
			    "staid=0x%02x, status=%d, tid=%d, bufsize=%d\n",
			    ni->ni_associd,
			    rni->staid, status, tid, bufsize);

			if (status == IEEE80211_STATUS_SUCCESS) {
				RT2860_SOFTC_LOCK(sc);

				rt2860_asic_add_ba_session(sc, wcid, tid);
				RT2860_SOFTC_UNLOCK(sc);

			}
			break;

		/* IEEE80211_ACTION_BA_DELBA */
		case IEEE80211_ACTION_BA_DELBA:
			baparamset = RT2860_SM(args[0],
			    IEEE80211_DELBAPS_TID) | args[1];
			tid = RT2860_MS(baparamset, IEEE80211_DELBAPS_TID);

			RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
				"sending DELBA request: associd=0x%04x, "
				"staid=0x%02x, tid=%d\n",
				ni->ni_associd, rni->staid, tid);

			if (RT2860_MS(baparamset, IEEE80211_DELBAPS_INIT) !=
			    IEEE80211_DELBAPS_INIT) {
				RT2860_SOFTC_LOCK(sc);

				rt2860_asic_del_ba_session(sc, wcid, tid);
				RT2860_SOFTC_UNLOCK(sc);

			}
			break;
		}
	}

	return sc->send_action(ni, cat, act, sa);
}

/*
 * rt2860_addba_response
 */
static int
rt2860_addba_response(struct ieee80211_node *ni,
    struct ieee80211_tx_ampdu *tap, int status, int baparamset, int batimeout)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_node *rni;
	ieee80211_seq seqno;
	int ret, tid, old_bufsize, new_bufsize;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rni = (struct rt2860_softc_node *) ni;

	tid = RT2860_MS(baparamset, IEEE80211_BAPS_TID);
	old_bufsize = RT2860_MS(baparamset, IEEE80211_BAPS_BUFSIZ);
	new_bufsize = old_bufsize;

	if (status == IEEE80211_STATUS_SUCCESS) {
		if (sc->mac_rev >= 0x28830300) {
			if (sc->mac_rev >= 0x30700200)
				new_bufsize = 13;
			else
				new_bufsize = 31;
		} else if (sc->mac_rev >= 0x28720200) {
			new_bufsize = 13;
		} else {
			new_bufsize = 7;
		}

		if (old_bufsize > new_bufsize) {
			baparamset &= ~IEEE80211_BAPS_BUFSIZ;
			baparamset = RT2860_SM(new_bufsize, IEEE80211_BAPS_BUFSIZ);
		}

		if (!(tap->txa_flags & IEEE80211_AGGR_RUNNING)) {
			sc->tx_ampdu_sessions++;

			if (sc->tx_ampdu_sessions == 1) {
				RT2860_SOFTC_LOCK(sc);

				rt2860_asic_updateprot(sc);
				RT2860_SOFTC_UNLOCK(sc);

			}
		}
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
	    "received ADDBA response: associd=0x%04x, staid=0x%02x, "
	    "status=%d, tid=%d, old bufsize=%d, new bufsize=%d\n",
	    ni->ni_associd, rni->staid,
	    status, tid, old_bufsize, new_bufsize);

	ret = sc->addba_response(ni, tap, status, baparamset, batimeout);

	if (status == IEEE80211_STATUS_SUCCESS) {
		seqno = ni->ni_txseqs[tid];

#ifdef	RT2860_SEND_BAR_WORKAROUND
		rt2860_send_bar(ni, tap, seqno);
#else
		ieee80211_send_bar(ni, tap, seqno);
#endif
	}

	return ret;
}

/*
 * rt2860_addba_stop
 */
static void
rt2860_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_node *rni;
	int tid;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rni = (struct rt2860_softc_node *) ni;

	tid = WME_AC_TO_TID(tap->txa_ac);

	RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
	    "stopping A-MPDU Tx: associd=0x%04x, staid=0x%02x, tid=%d\n",
	    ni->ni_associd, rni->staid, tid);

	if (tap->txa_flags & IEEE80211_AGGR_RUNNING) {
		if (sc->tx_ampdu_sessions > 0) {
			sc->tx_ampdu_sessions--;

			if (sc->tx_ampdu_sessions == 0) {
				RT2860_SOFTC_LOCK(sc);
				rt2860_asic_updateprot(sc);
				RT2860_SOFTC_UNLOCK(sc);
			}
		} else {
			device_printf(sc->sc_dev,
			    "number of A-MPDU Tx sessions cannot be negative\n");
		}
	}

	sc->addba_stop(ni, tap);
}

/*
 * rt2860_ampdu_rx_start
 */
static int
rt2860_ampdu_rx_start(struct ieee80211_node *ni,
    struct ieee80211_rx_ampdu *rap, int baparamset, int batimeout,
    int baseqctl)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_node *rni;
	int tid;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rni = (struct rt2860_softc_node *) ni;

	tid = RT2860_MS(baparamset, IEEE80211_BAPS_TID);

	RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
	    "starting A-MPDU Rx: associd=0x%04x, staid=0x%02x, tid=%d\n",
	    ni->ni_associd, rni->staid, tid);

	if (!(rap->rxa_flags & IEEE80211_AGGR_RUNNING))
		sc->rx_ampdu_sessions++;

	return sc->ampdu_rx_start(ni, rap, baparamset, batimeout, baseqctl);
}

/*
 * rt2860_ampdu_rx_stop
 */
static void
rt2860_ampdu_rx_stop(struct ieee80211_node *ni,
    struct ieee80211_rx_ampdu *rap)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct rt2860_softc_node *rni;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rni = (struct rt2860_softc_node *) ni;

	RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
		"stopping A-MPDU Rx: associd=0x%04x, staid=0x%02x\n",
		 ni->ni_associd, rni->staid);

	if (rap->rxa_flags & IEEE80211_AGGR_RUNNING) {
		if (sc->rx_ampdu_sessions > 0)
			sc->rx_ampdu_sessions--;
		else
			device_printf(sc->sc_dev, "number of A-MPDU Rx "
			    "sessions cannot be negative\n");
	}

	sc->ampdu_rx_stop(ni, rap);
}

#ifdef	RT2860_SEND_BAR_WORKAROUND
/*
 * rt2860_send_bar
 */
static int
rt2860_send_bar(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap,
    ieee80211_seq seqno)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct ieee80211vap *vap;
	struct ieee80211_frame_bar *bar;
	struct rt2860_softc_node *rni;
	struct mbuf *m;
	uint16_t barctl, barseqctl;
	uint8_t *frm;
	int ret, tid;

	ic = ni->ni_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	vap = ni->ni_vap;
	rni = (struct rt2860_softc_node *) ni;

	if (!(tap->txa_flags & IEEE80211_AGGR_RUNNING))
		return EINVAL;

	m = ieee80211_getmgtframe(&frm, ic->ic_headroom, sizeof(struct ieee80211_frame_bar));
	if (m == NULL)
		return ENOMEM;

	bar = mtod(m, struct ieee80211_frame_bar *);

	bar->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_BAR;
	bar->i_fc[1] = 0;

	IEEE80211_ADDR_COPY(bar->i_ra, ni->ni_macaddr);
	IEEE80211_ADDR_COPY(bar->i_ta, vap->iv_myaddr);

	tid = WME_AC_TO_TID(tap->txa_ac);

	barctl = (tap->txa_flags & IEEE80211_AGGR_IMMEDIATE ?  0 : IEEE80211_BAR_NOACK) |
		IEEE80211_BAR_COMP |
		RT2860_SM(tid, IEEE80211_BAR_TID);
	barseqctl = RT2860_SM(seqno, IEEE80211_BAR_SEQ_START);

	bar->i_ctl = htole16(barctl);
	bar->i_seq = htole16(barseqctl);

	m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_bar);

	tap->txa_start = seqno;

	ieee80211_ref_node(ni);

	RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
		"sending BAR: associd=0x%04x, staid=0x%02x, tid=%d, seqno=%d\n",
		 ni->ni_associd, rni->staid, tid, seqno);

	ret = ic->ic_raw_xmit(ni, m, NULL);
	if (ret != 0)
		ieee80211_free_node(ni);

	return ret;
}
#endif

/*
 * rt2860_amrr_update_iter_func
 */
static void
rt2860_amrr_update_iter_func(void *arg, struct ieee80211_node *ni)
{
	struct rt2860_softc *sc;
	struct ieee80211com *ic;
	struct ifnet *ifp;
	struct ieee80211vap *vap;
	struct rt2860_softc_vap *rvap;
	struct rt2860_softc_node *rni;
	uint8_t wcid;

	vap = arg;
	ic = vap->iv_ic;
	ifp = ic->ic_ifp;
	sc = ifp->if_softc;
	rvap = (struct rt2860_softc_vap *) vap;
	rni = (struct rt2860_softc_node *) ni;

	/* only associated stations */

	if ((ni->ni_vap == vap) && (rni->staid != 0)) {
		wcid = rni->staid;

		RT2860_DPRINTF(sc, RT2860_DEBUG_RATE,
			"AMRR node: staid=0x%02x, txcnt=%d, success=%d, retrycnt=%d\n",
			rni->staid, sc->amrr_node[wcid].txcnt, sc->amrr_node[wcid].success, sc->amrr_node[wcid].retrycnt);

		rt2860_amrr_choose(ni, &sc->amrr_node[wcid]);

		RT2860_DPRINTF(sc, RT2860_DEBUG_RATE,
			"%snode Tx rate: associd=0x%04x, staid=0x%02x, rate=0x%02x, max rate=0x%02x\n",
			(ni->ni_flags & IEEE80211_NODE_HT) ? "HT " : "",
			ni->ni_associd, rni->staid, ni->ni_txrate,
			(ni->ni_flags & IEEE80211_NODE_HT) ?
				(ni->ni_htrates.rs_rates[ni->ni_htrates.rs_nrates - 1] | IEEE80211_RATE_MCS) :
				(ni->ni_rates.rs_rates[ni->ni_rates.rs_nrates - 1] & IEEE80211_RATE_VAL));
	}
}

/*
 * rt2860_periodic
 */
static void
rt2860_periodic(void *arg)
{
	struct rt2860_softc *sc;

	sc = arg;

	RT2860_DPRINTF(sc, RT2860_DEBUG_PERIODIC, "periodic\n");

	taskqueue_enqueue(sc->taskqueue, &sc->periodic_task);
}

/*
 * rt2860_tx_watchdog
 */
static void
rt2860_tx_watchdog(void *arg)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;

	sc = arg;
	ifp = sc->sc_ifp;

	if (sc->sc_invalid)		/* card ejected */
		return;

	if (sc->tx_timer == 0)
		return;

	if (--sc->tx_timer == 0) {
		device_printf(sc->sc_dev, "Tx watchdog timeout: resetting\n");

		rt2860_stop_locked(sc);
		rt2860_init_locked(sc);

		ifp->if_oerrors++;

		sc->tx_watchdog_timeouts++;
	}

	callout_reset(&sc->tx_watchdog_ch, hz, rt2860_tx_watchdog, sc);
}

/*
 * rt2860_staid_alloc
 */
static int
rt2860_staid_alloc(struct rt2860_softc *sc, int aid)
{
	int staid;

	if ((aid > 0 && aid < RT2860_SOFTC_STAID_COUNT) && isclr(sc->staid_mask, aid)) {
		staid = aid;
	} else {
		for (staid = 1; staid < RT2860_SOFTC_STAID_COUNT; staid++) {
			if (isclr(sc->staid_mask, staid))
				break;
		}
	}

	setbit(sc->staid_mask, staid);

	return staid;
}

/*
 * rt2860_staid_delete
 */
static void
rt2860_staid_delete(struct rt2860_softc *sc, int staid)
{

	clrbit(sc->staid_mask, staid);
}

/*
 * rt2860_asic_set_bssid
 */
static void
rt2860_asic_set_bssid(struct rt2860_softc *sc, const uint8_t *bssid)
{
	uint32_t tmp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE,
		"set bssid: bssid=%s\n",
		ether_sprintf(bssid));

	tmp = bssid[0] | (bssid[1]) << 8 | (bssid[2] << 16) | (bssid[3] << 24);

	RAL_WRITE(sc, RT2860_REG_BSSID_DW0, tmp);

	tmp = bssid[4] | (bssid[5] << 8);

	RAL_WRITE(sc, RT2860_REG_BSSID_DW1, tmp);
}

/*
 * rt2860_asic_set_macaddr
 */
static void
rt2860_asic_set_macaddr(struct rt2860_softc *sc, const uint8_t *addr)
{
	uint32_t tmp;

	tmp = addr[0] | (addr[1] << 8) | (addr[2] << 16) | (addr[3] << 24);

	RAL_WRITE(sc, RT2860_REG_ADDR_DW0, tmp);

	tmp = addr[4] | (addr[5] << 8) | (0xff << 16);

	RAL_WRITE(sc, RT2860_REG_ADDR_DW1, tmp);
}

/*
 * rt2860_asic_enable_tsf_sync
 */
static void
rt2860_asic_enable_tsf_sync(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;
	uint32_t tmp;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = TAILQ_FIRST(&ic->ic_vaps);

	RT2860_DPRINTF(sc, RT2860_DEBUG_BEACON, "enabling TSF\n");

	tmp = RAL_READ(sc, RT2860_REG_BCN_TIME_CFG);

	tmp &= ~0x1fffff;
	tmp |= vap->iv_bss->ni_intval * 16;
	tmp |= (RT2860_REG_TSF_TIMER_ENABLE | RT2860_REG_TBTT_TIMER_ENABLE);

	if (vap->iv_opmode == IEEE80211_M_STA) {
		tmp |= (RT2860_REG_TSF_SYNC_MODE_STA << RT2860_REG_TSF_SYNC_MODE_SHIFT);
	} else if (vap->iv_opmode == IEEE80211_M_IBSS) {
		tmp |= RT2860_REG_BCN_TX_ENABLE;
		tmp |= (RT2860_REG_TSF_SYNC_MODE_IBSS << RT2860_REG_TSF_SYNC_MODE_SHIFT);
	} else if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
		tmp |= RT2860_REG_BCN_TX_ENABLE;
		tmp |= (RT2860_REG_TSF_SYNC_MODE_HOSTAP << RT2860_REG_TSF_SYNC_MODE_SHIFT);
	}

	RAL_WRITE(sc, RT2860_REG_BCN_TIME_CFG, tmp);
}

/*
 * rt2860_asic_disable_tsf_sync
 */
static void
rt2860_asic_disable_tsf_sync(struct rt2860_softc *sc)
{
	uint32_t tmp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_BEACON, "disabling TSF\n");

	tmp = RAL_READ(sc, RT2860_REG_BCN_TIME_CFG);

	tmp &= ~(RT2860_REG_BCN_TX_ENABLE |
		RT2860_REG_TSF_TIMER_ENABLE |
		RT2860_REG_TBTT_TIMER_ENABLE);

	tmp &= ~(RT2860_REG_TSF_SYNC_MODE_MASK << RT2860_REG_TSF_SYNC_MODE_SHIFT);
	tmp |= (RT2860_REG_TSF_SYNC_MODE_DISABLE << RT2860_REG_TSF_SYNC_MODE_SHIFT);

	RAL_WRITE(sc, RT2860_REG_BCN_TIME_CFG, tmp);
}

/*
 * rt2860_asic_enable_mrr
 */
static void
rt2860_asic_enable_mrr(struct rt2860_softc *sc)
{
#define CCK(mcs)	(mcs)
#define OFDM(mcs)	((1 << 3) | (mcs))
#define HT(mcs)		(mcs)

	RAL_WRITE(sc, RT2860_REG_TX_LG_FBK_CFG0,
		(OFDM(6) << 28) |	/* 54 -> 48 */
		(OFDM(5) << 24) |	/* 48 -> 36 */
		(OFDM(4) << 20) |	/* 36 -> 24 */
		(OFDM(3) << 16) |	/* 24 -> 18 */
		(OFDM(2) << 12) |	/* 18 -> 12 */
		(OFDM(1) << 8)  |	/* 12 -> 9 */
		(OFDM(0) << 4)  |	/*  9 -> 6 */
		OFDM(0));			/*  6 -> 6 */

	RAL_WRITE(sc, RT2860_REG_TX_LG_FBK_CFG1,
		(CCK(2) << 12) |	/* 11  -> 5.5 */
		(CCK(1) << 8)  |	/* 5.5 -> 2 */
		(CCK(0) << 4)  |	/*   2 -> 1 */
		CCK(0));			/*   1 -> 1 */

	RAL_WRITE(sc, RT2860_REG_TX_HT_FBK_CFG0,
		(HT(6) << 28) |
		(HT(5) << 24) |
		(HT(4) << 20) |
		(HT(3) << 16) |
		(HT(2) << 12) |
		(HT(1) << 8)  |
		(HT(0) << 4)  |
		HT(0));

	RAL_WRITE(sc, RT2860_REG_TX_HT_FBK_CFG1,
		(HT(14) << 28) |
		(HT(13) << 24) |
		(HT(12) << 20) |
		(HT(11) << 16) |
		(HT(10) << 12) |
		(HT(9) << 8)   |
		(HT(8) << 4)   |
		HT(7));

#undef HT
#undef OFDM
#undef CCK
}

/*
 * rt2860_asic_set_txpreamble
 */
static void
rt2860_asic_set_txpreamble(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	uint32_t tmp;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE,
		"%s short Tx preamble\n",
		(ic->ic_flags & IEEE80211_F_SHPREAMBLE) ? "enabling" : "disabling");

	tmp = RAL_READ(sc, RT2860_AUTO_RSP_CFG);

	tmp &= ~RT2860_CCK_SHORT_EN;

	if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
		tmp |= RT2860_CCK_SHORT_EN;

	RAL_WRITE(sc, RT2860_AUTO_RSP_CFG, tmp);
}

/*
 * rt2860_asic_set_basicrates
 */
static void
rt2860_asic_set_basicrates(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	if (ic->ic_curmode == IEEE80211_MODE_11B)
		RAL_WRITE(sc, RT2860_LEGACY_BASIC_RATE, 0xf);
	else if (ic->ic_curmode == IEEE80211_MODE_11A)
		RAL_WRITE(sc, RT2860_LEGACY_BASIC_RATE, 0x150);
	else
		RAL_WRITE(sc, RT2860_LEGACY_BASIC_RATE, 0x15f);
}

/*
 * rt2860_asic_update_rtsthreshold
 */
static void
rt2860_asic_update_rtsthreshold(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;
	uint32_t tmp;
	uint16_t threshold;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = TAILQ_FIRST(&ic->ic_vaps);

	if (vap == NULL)
		threshold = IEEE80211_RTS_MAX;
	else if (vap->iv_flags_ht & IEEE80211_FHT_AMSDU_TX)
		threshold = 0x1000;
	else
		threshold = vap->iv_rtsthreshold;

	RT2860_DPRINTF(sc, RT2860_DEBUG_PROT,
		"updating RTS threshold: %d\n",
		 threshold);

	tmp = RAL_READ(sc, RT2860_TX_RTS_CFG);

	tmp &= ~(RT2860_REG_TX_RTS_THRESHOLD_MASK << RT2860_REG_TX_RTS_THRESHOLD_SHIFT);

	tmp |= ((threshold & RT2860_REG_TX_RTS_THRESHOLD_MASK) <<
		RT2860_REG_TX_RTS_THRESHOLD_SHIFT);

	RAL_WRITE(sc, RT2860_TX_RTS_CFG, tmp);
}

/*
 * rt2860_asic_update_txpower
 */
static void
rt2860_asic_update_txpower(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	uint32_t *txpow_rate;
	int8_t delta;
	uint8_t val;
	uint32_t tmp;
	int i;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE,
		"updating Tx power: %d\n",
		 ic->ic_txpowlimit);

	if (!IEEE80211_IS_CHAN_HT40(ic->ic_curchan)) {
		txpow_rate = sc->txpow_rate_20mhz;
	} else {
		if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan))
			txpow_rate = sc->txpow_rate_40mhz_2ghz;
		else
			txpow_rate = sc->txpow_rate_40mhz_5ghz;
	}

	delta = 0;

	val = rt2860_io_bbp_read(sc, 1);
	val &= 0xfc;

	if (ic->ic_txpowlimit > 90) {
		/* do nothing */
	} else if (ic->ic_txpowlimit > 60) {
		delta -= 1;
	} else if (ic->ic_txpowlimit > 30) {
		delta -= 3;
	} else if (ic->ic_txpowlimit > 15) {
		val |= 0x1;
	} else if (ic->ic_txpowlimit > 9) {
		val |= 0x1;
		delta -= 3;
	} else {
		val |= 0x2;
	}

	rt2860_io_bbp_write(sc, 1, val);

	for (i = 0; i < RT2860_SOFTC_TXPOW_RATE_COUNT; i++) {
		if (txpow_rate[i] == 0xffffffff)
			continue;
		tmp = rt2860_read_eeprom_txpow_rate_add_delta(txpow_rate[i], delta);
		RAL_WRITE(sc, RT2860_REG_TX_PWR_CFG(i), tmp);
	}
}

/*
 * rt2860_asic_update_promisc
 */
static void
rt2860_asic_update_promisc(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	uint32_t tmp;

	ifp = sc->sc_ifp;

	device_printf(sc->sc_dev, "%s promiscuous mode\n",
	    (ifp->if_flags & IFF_PROMISC) ? "entering" : "leaving");

	tmp = RAL_READ(sc, RT2860_REG_RX_FILTER_CFG);

	tmp &= ~RT2860_DROP_UC_NOME;

	if (!(ifp->if_flags & IFF_PROMISC))
		tmp |= RT2860_DROP_UC_NOME;

	RAL_WRITE(sc, RT2860_REG_RX_FILTER_CFG, tmp);
}

/*
 * rt2860_asic_updateprot
 */
static void
rt2860_asic_updateprot(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;
	uint32_t cck_prot, ofdm_prot, mm20_prot, mm40_prot, gf20_prot, gf40_prot;
	uint8_t htopmode;
	enum ieee80211_protmode htprotmode;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = TAILQ_FIRST(&ic->ic_vaps);

	/* CCK frame protection */

	cck_prot = RT2860_RTSTH_EN | RT2860_PROT_NAV_SHORT |
		RT2860_TXOP_ALLOW_ALL;

	/* set up protection frame phy mode and rate (MCS code) */

	if (ic->ic_curmode == IEEE80211_MODE_11A)
		cck_prot |= (RT2860_REG_PROT_PHYMODE_OFDM << RT2860_REG_PROT_PHYMODE_SHIFT) |
			(0 << RT2860_REG_PROT_MCS_SHIFT);
	else
		cck_prot |= ((RT2860_REG_PROT_PHYMODE_CCK << RT2860_REG_PROT_PHYMODE_SHIFT) |
			(3 << RT2860_REG_PROT_MCS_SHIFT));

	RAL_WRITE(sc, RT2860_CCK_PROT_CFG, cck_prot);

	/* OFDM frame protection */

	ofdm_prot = RT2860_REG_RTSTH_ENABLE | RT2860_PROT_NAV_SHORT |
		RT2860_TXOP_ALLOW_ALL;

	if (ic->ic_flags & IEEE80211_F_USEPROT) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_PROT,
			"updating protection mode: b/g protection mode=%s\n",
			(ic->ic_protmode == IEEE80211_PROT_RTSCTS) ? "RTS/CTS" :
				((ic->ic_protmode == IEEE80211_PROT_CTSONLY) ? "CTS-to-self" : "none"));

		if (ic->ic_protmode == IEEE80211_PROT_RTSCTS)
			ofdm_prot |= RT2860_PROT_CTRL_RTS_CTS;
		else if (ic->ic_protmode == IEEE80211_PROT_CTSONLY)
			ofdm_prot |= RT2860_PROT_CTRL_CTS;
	} else {
		RT2860_DPRINTF(sc, RT2860_DEBUG_PROT,
			"updating protection mode: b/g protection mode=%s\n",
			 "none");
	}

	RAL_WRITE(sc, RT2860_OFDM_PROT_CFG, ofdm_prot);

	/* HT frame protection */

	if ((vap != NULL) && (vap->iv_opmode == IEEE80211_M_STA) && (vap->iv_state == IEEE80211_S_RUN))
		htopmode = vap->iv_bss->ni_htopmode;
	else
		htopmode = ic->ic_curhtprotmode;

	htprotmode = ic->ic_htprotmode;

	/* force HT mixed mode and RTS/CTS protection if A-MPDU Tx aggregation is enabled */

	if (sc->tx_ampdu_sessions > 0) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_PROT, "updating protection "
		    "mode: forcing HT mixed mode and RTS/CTS protection\n");

		htopmode = IEEE80211_HTINFO_OPMODE_MIXED;
		htprotmode = IEEE80211_PROT_RTSCTS;
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_PROT,
		"updating protection mode: HT operation mode=0x%02x, protection mode=%s\n",
		htopmode & IEEE80211_HTINFO_OPMODE,
		(htprotmode == IEEE80211_PROT_RTSCTS) ? "RTS/CTS" :
			((htprotmode == IEEE80211_PROT_CTSONLY) ? "CTS-to-self" : "none"));

	switch (htopmode & IEEE80211_HTINFO_OPMODE) {
	/* IEEE80211_HTINFO_OPMODE_HT20PR */
	case IEEE80211_HTINFO_OPMODE_HT20PR:
		mm20_prot = RT2860_PROT_NAV_SHORT |
			RT2860_TXOP_ALLOW_CCK | RT2860_TXOP_ALLOW_OFDM |
			RT2860_TXOP_ALLOW_MM20 | RT2860_TXOP_ALLOW_GF20 |
			(RT2860_REG_PROT_PHYMODE_OFDM << RT2860_REG_PROT_PHYMODE_SHIFT) |
			(4 << RT2860_REG_PROT_MCS_SHIFT);

		gf20_prot = mm20_prot;

		mm40_prot = RT2860_PROT_NAV_SHORT | RT2860_TXOP_ALLOW_ALL |
			(RT2860_REG_PROT_PHYMODE_OFDM << RT2860_REG_PROT_PHYMODE_SHIFT) |
			(0x84 << RT2860_REG_PROT_MCS_SHIFT);

		if (htprotmode == IEEE80211_PROT_RTSCTS)
			mm40_prot |= RT2860_PROT_CTRL_RTS_CTS;
		else if (htprotmode == IEEE80211_PROT_CTSONLY)
			mm40_prot |= RT2860_PROT_CTRL_CTS;

		gf40_prot = mm40_prot;
		break;

	/* IEEE80211_HTINFO_OPMODE_MIXED */
	case IEEE80211_HTINFO_OPMODE_MIXED:
		mm20_prot = RT2860_PROT_NAV_SHORT |
			RT2860_TXOP_ALLOW_CCK | RT2860_TXOP_ALLOW_OFDM |
			RT2860_TXOP_ALLOW_MM20 | RT2860_TXOP_ALLOW_GF20;

		if (ic->ic_flags & IEEE80211_F_USEPROT)
			mm20_prot |= (RT2860_REG_PROT_PHYMODE_CCK << RT2860_REG_PROT_PHYMODE_SHIFT) |
				(3 << RT2860_REG_PROT_MCS_SHIFT);
		else
			mm20_prot |= (RT2860_REG_PROT_PHYMODE_OFDM << RT2860_REG_PROT_PHYMODE_SHIFT) |
				(4 << RT2860_REG_PROT_MCS_SHIFT);

		if (htprotmode == IEEE80211_PROT_RTSCTS)
			mm20_prot |= RT2860_PROT_CTRL_RTS_CTS;
		else if (htprotmode == IEEE80211_PROT_CTSONLY)
			mm20_prot |= RT2860_PROT_CTRL_CTS;

		gf20_prot = mm20_prot;

		mm40_prot = RT2860_PROT_NAV_SHORT | RT2860_TXOP_ALLOW_ALL;

		if (ic->ic_flags & IEEE80211_F_USEPROT)
			mm40_prot |= (RT2860_REG_PROT_PHYMODE_CCK << RT2860_REG_PROT_PHYMODE_SHIFT) |
				(3 << RT2860_REG_PROT_MCS_SHIFT);
		else
			mm40_prot |= (RT2860_REG_PROT_PHYMODE_OFDM << RT2860_REG_PROT_PHYMODE_SHIFT) |
				(0x84 << RT2860_REG_PROT_MCS_SHIFT);

		if (htprotmode == IEEE80211_PROT_RTSCTS)
			mm40_prot |= RT2860_PROT_CTRL_RTS_CTS;
		else if (htprotmode == IEEE80211_PROT_CTSONLY)
			mm40_prot |= RT2860_PROT_CTRL_CTS;

		gf40_prot = mm40_prot;
		break;

	/*
	 * IEEE80211_HTINFO_OPMODE_PURE
	 * IEEE80211_HTINFO_OPMODE_PROTOPT
	 */
	case IEEE80211_HTINFO_OPMODE_PURE:
	case IEEE80211_HTINFO_OPMODE_PROTOPT:
	default:
		mm20_prot = RT2860_PROT_NAV_SHORT |
			RT2860_TXOP_ALLOW_CCK | RT2860_TXOP_ALLOW_OFDM |
			RT2860_TXOP_ALLOW_MM20 | RT2860_TXOP_ALLOW_GF20 |
			(RT2860_REG_PROT_PHYMODE_OFDM << RT2860_REG_PROT_PHYMODE_SHIFT) |
			(4 << RT2860_REG_PROT_MCS_SHIFT);

		gf20_prot = mm20_prot;

		mm40_prot = RT2860_PROT_NAV_SHORT |
			RT2860_TXOP_ALLOW_ALL |
			(RT2860_REG_PROT_PHYMODE_OFDM << RT2860_REG_PROT_PHYMODE_SHIFT) |
			(0x84 << RT2860_REG_PROT_MCS_SHIFT);

		gf40_prot = mm40_prot;
		break;
	}

	RAL_WRITE(sc, RT2860_MM20_PROT_CFG, mm20_prot);
	RAL_WRITE(sc, RT2860_MM40_PROT_CFG, mm40_prot);
	RAL_WRITE(sc, RT2860_GF20_PROT_CFG, gf20_prot);
	RAL_WRITE(sc, RT2860_GF40_PROT_CFG, gf40_prot);
}

/*
 * rt2860_asic_updateslot
 */
static void
rt2860_asic_updateslot(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;
	uint32_t tmp;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = TAILQ_FIRST(&ic->ic_vaps);

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATE,
		"%s short slot time\n",
		((ic->ic_flags & IEEE80211_F_SHSLOT) ||
		 ((vap != NULL) && (vap->iv_flags & IEEE80211_F_BURST))) ? "enabling" : "disabling");

	tmp = RAL_READ(sc, RT2860_BKOFF_SLOT_CFG);

	tmp &= ~0xff;

	if ((ic->ic_flags & IEEE80211_F_SHSLOT) ||
		((vap != NULL) && (vap->iv_flags & IEEE80211_F_BURST)))
		tmp |= IEEE80211_DUR_SHSLOT;
	else
		tmp |= IEEE80211_DUR_SLOT;

	RAL_WRITE(sc, RT2860_BKOFF_SLOT_CFG, tmp);
}

/*
 * rt2860_asic_wme_update
 */
static void
rt2860_asic_wme_update(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211_wme_state *wme;
	const struct wmeParams *wmep;
	int i;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	wme = &ic->ic_wme;
	wmep = wme->wme_chanParams.cap_wmeParams;

	RT2860_DPRINTF(sc, RT2860_DEBUG_WME,
		"wme update: WME_AC_VO=%d/%d/%d/%d, WME_AC_VI=%d/%d/%d/%d, "
		"WME_AC_BK=%d/%d/%d/%d, WME_AC_BE=%d/%d/%d/%d\n",
		wmep[WME_AC_VO].wmep_aifsn,
		wmep[WME_AC_VO].wmep_logcwmin, wmep[WME_AC_VO].wmep_logcwmax,
		wmep[WME_AC_VO].wmep_txopLimit,
		wmep[WME_AC_VI].wmep_aifsn,
		wmep[WME_AC_VI].wmep_logcwmin, wmep[WME_AC_VI].wmep_logcwmax,
		wmep[WME_AC_VI].wmep_txopLimit,
		wmep[WME_AC_BK].wmep_aifsn,
		wmep[WME_AC_BK].wmep_logcwmin, wmep[WME_AC_BK].wmep_logcwmax,
		wmep[WME_AC_BK].wmep_txopLimit,
		wmep[WME_AC_BE].wmep_aifsn,
		wmep[WME_AC_BE].wmep_logcwmin, wmep[WME_AC_BE].wmep_logcwmax,
		wmep[WME_AC_BE].wmep_txopLimit);

	for (i = 0; i < WME_NUM_AC; i++)
		RAL_WRITE(sc, RT2860_REG_TX_EDCA_AC_CFG(i),
			(wmep[i].wmep_logcwmax << 16) | (wmep[i].wmep_logcwmin << 12) |
			(wmep[i].wmep_aifsn << 8) | wmep[i].wmep_txopLimit);

	RAL_WRITE(sc, RT2860_WMM_AIFSN_CFG,
		(wmep[WME_AC_VO].wmep_aifsn << 12) | (wmep[WME_AC_VI].wmep_aifsn << 8) |
		(wmep[WME_AC_BK].wmep_aifsn << 4) | wmep[WME_AC_BE].wmep_aifsn);

	RAL_WRITE(sc, RT2860_WMM_CWMIN_CFG,
		(wmep[WME_AC_VO].wmep_logcwmin << 12) | (wmep[WME_AC_VI].wmep_logcwmin << 8) |
		(wmep[WME_AC_BK].wmep_logcwmin << 4) | wmep[WME_AC_BE].wmep_logcwmin);

	RAL_WRITE(sc, RT2860_WMM_CWMAX_CFG,
		(wmep[WME_AC_VO].wmep_logcwmax << 12) | (wmep[WME_AC_VI].wmep_logcwmax << 8) |
		(wmep[WME_AC_BK].wmep_logcwmax << 4) | wmep[WME_AC_BE].wmep_logcwmax);

	RAL_WRITE(sc, RT2860_WMM_TXOP0_CFG,
		(wmep[WME_AC_BK].wmep_txopLimit << 16) | wmep[WME_AC_BE].wmep_txopLimit);

	RAL_WRITE(sc, RT2860_WMM_TXOP1_CFG,
		(wmep[WME_AC_VO].wmep_txopLimit << 16) | wmep[WME_AC_VI].wmep_txopLimit);
}

/*
 * rt2860_asic_update_beacon
 */
static void
rt2860_asic_update_beacon(struct rt2860_softc *sc, struct ieee80211vap *vap,
    struct rt2860_txwi *txwi)
{
	struct rt2860_softc_vap *rvap;
	struct mbuf *m;
	uint32_t tmp;

	rvap = (struct rt2860_softc_vap *) vap;

	m = rvap->beacon_mbuf;

	/* disable temporarily TSF sync */

	tmp = RAL_READ(sc, RT2860_REG_BCN_TIME_CFG);

	tmp &= ~(RT2860_REG_BCN_TX_ENABLE |
		RT2860_REG_TSF_TIMER_ENABLE |
		RT2860_REG_TBTT_TIMER_ENABLE);

	RAL_WRITE(sc, RT2860_REG_BCN_TIME_CFG, tmp);

	/* write Tx wireless info and beacon frame to on-chip memory */

	RAL_WRITE_REGION_1(sc, RT2860_REG_BEACON_BASE(0),
		(uint8_t *)txwi, sizeof(struct rt2860_txwi));

	RAL_WRITE_REGION_1(sc, RT2860_REG_BEACON_BASE(0) + sizeof(struct rt2860_txwi),
		mtod(m, uint8_t *), m->m_pkthdr.len);

	/* enable again TSF sync */

	tmp = RAL_READ(sc, RT2860_REG_BCN_TIME_CFG);

	tmp |= (RT2860_REG_BCN_TX_ENABLE |
		RT2860_REG_TSF_TIMER_ENABLE |
		RT2860_REG_TBTT_TIMER_ENABLE);

	RAL_WRITE(sc, RT2860_REG_BCN_TIME_CFG, tmp);
}

/*
 * rt2860_asic_clear_keytables
 */
static void
rt2860_asic_clear_keytables(struct rt2860_softc *sc)
{
	int i;

	/* clear Rx WCID search table (entries = 256, entry size = 8) */
	for (i = 0; i < 256; i++) {
		RAL_WRITE(sc, RT2860_REG_WCID(i), 0xffffffff);
		RAL_WRITE(sc, RT2860_REG_WCID(i) + 4, 0x0000ffff);
	}

	/* clear WCID attribute table (entries = 256, entry size = 4) */
	RAL_SET_REGION_4(sc, RT2860_REG_WCID_ATTR(0), RT2860_RX_PKEY_EN, 256);

	/* clear IV/EIV table (entries = 256, entry size = 8) */
	RAL_SET_REGION_4(sc, RT2860_REG_IVEIV(0), 0, 2 * 256);

	/* clear pairwise key table (entries = 64, entry size = 32) */
	RAL_SET_REGION_4(sc, RT2860_REG_PKEY(0), 0, 8 * 64);

	/* clear shared key table (entries = 32, entry size = 32) */
	RAL_SET_REGION_4(sc, RT2860_REG_SKEY(0, 0), 0, 8 * 32);

	/* clear shared key mode (entries = 32, entry size = 2) */
	RAL_SET_REGION_4(sc, RT2860_REG_SKEY_MODE(0), 0, 16);
}

/*
 * rt2860_asic_add_ba_session
 */
static void
rt2860_asic_add_ba_session(struct rt2860_softc *sc, uint8_t wcid, int tid)
{
	uint32_t tmp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
		"adding BA session: wcid=0x%02x, tid=%d\n",
		 wcid, tid);

	tmp = RAL_READ(sc, RT2860_REG_WCID(wcid) + 4);
	tmp |= (0x10000 << tid);
	RAL_WRITE(sc, RT2860_REG_WCID(wcid) + 4, tmp);
}

/*
 * rt2860_asic_del_ba_session
 */
static void
rt2860_asic_del_ba_session(struct rt2860_softc *sc, uint8_t wcid, int tid)
{
	uint32_t tmp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_BA,
		"deleting BA session: wcid=0x%02x, tid=%d\n",
		 wcid, tid);

	tmp = RAL_READ(sc, RT2860_REG_WCID(wcid) + 4);

	tmp &= ~(0x10000 << tid);

	RAL_WRITE(sc, RT2860_REG_WCID(wcid) + 4, tmp);
}

/*
 * rt2860_beacon_alloc
 */
static int
rt2860_beacon_alloc(struct rt2860_softc *sc, struct ieee80211vap *vap)
{
	struct ieee80211com *ic;
	struct rt2860_softc_vap *rvap;
	struct mbuf *m;
	struct rt2860_txwi txwi;
	uint8_t rate, mcs;

	ic = vap->iv_ic;
	rvap = (struct rt2860_softc_vap *) vap;

	m = ieee80211_beacon_alloc(vap->iv_bss, &rvap->beacon_offsets);
	if (m == NULL)
		return ENOMEM;

	rate = IEEE80211_IS_CHAN_5GHZ(vap->iv_bss->ni_chan) ? 12 : 2;
	mcs = rt2860_rate2mcs(rate);

	RT2860_DPRINTF(sc, RT2860_DEBUG_BEACON,
		"beacon allocate: mcs=0x%02x\n",
		 mcs);

	memset(&txwi, 0, sizeof(struct rt2860_txwi));

	txwi.wcid = RT2860_WCID_RESERVED;
	txwi.len = htole16(m->m_pkthdr.len);
	txwi.txop = RT2860_TX_TXOP_HT;
	txwi.flags |= RT2860_TX_TS;
	txwi.xflags |= RT2860_TX_NSEQ;

	if (rate == 2) {
		txwi.phy = RT2860_PHY_CCK;

		if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE))
			mcs |= RT2860_PHY_SHPRE;
	} else {
		txwi.phy = RT2860_PHY_OFDM;
	}

	txwi.phy |= (mcs & RT2860_PHY_MCS);

	if (rvap->beacon_mbuf != NULL) {
		m_free(rvap->beacon_mbuf);
		rvap->beacon_mbuf = NULL;
	}

	rvap->beacon_mbuf = m;

	rt2860_asic_update_beacon(sc, vap, &txwi);

	return 0;
}

/*
 * rt2860_rxrate
 */
static uint8_t
rt2860_rxrate(struct rt2860_rxwi *rxwi)
{
	uint8_t mcs, phymode;
	uint8_t rate;

	mcs = (rxwi->bw_mcs >> RT2860_RXWI_MCS_SHIFT) & RT2860_RXWI_MCS_MASK;
	phymode = (rxwi->phymode_stbc_shortgi >> RT2860_RXWI_PHYMODE_SHIFT) &
		RT2860_RXWI_PHYMODE_MASK;

	rate = 2;

	switch (phymode) {
	case RT2860_RXWI_PHYMODE_CCK:
		switch (mcs & ~RT2860_RXWI_MCS_SHOTPRE) {
		case 0: rate = 2; break;	/* 1 Mbps */
		case 1: rate = 4; break;	/* 2 MBps */
		case 2: rate = 11; break;	/* 5.5 Mbps */
		case 3: rate = 22; break;	/* 11 Mbps */
		}
		break;

	case RT2860_RXWI_PHYMODE_OFDM:
		switch (mcs) {
		case 0: rate = 12; break;	/* 6 Mbps */
		case 1: rate = 18; break;	/* 9 Mbps */
		case 2: rate = 24; break;	/* 12 Mbps */
		case 3: rate = 36; break;	/* 18 Mbps */
		case 4: rate = 48; break;	/* 24 Mbps */
		case 5: rate = 72; break;	/* 36 Mbps */
		case 6: rate = 96; break;	/* 48 Mbps */
		case 7: rate = 108; break;	/* 54 Mbps */
		}
		break;

	case RT2860_RXWI_PHYMODE_HT_MIXED:
	case RT2860_RXWI_PHYMODE_HT_GF:
		break;
	}

	return rate;
}

/*
 * rt2860_maxrssi_rxpath
 */
static uint8_t
rt2860_maxrssi_rxpath(struct rt2860_softc *sc, const struct rt2860_rxwi *rxwi)
{
	uint8_t rxpath;

	rxpath = 0;

	if (sc->nrxpath > 1)
		if (rxwi->rssi[1] > rxwi->rssi[rxpath])
			rxpath = 1;

	if (sc->nrxpath > 2)
		if (rxwi->rssi[2] > rxwi->rssi[rxpath])
			rxpath = 2;

	return rxpath;
}

/*
 * rt2860_rssi2dbm
 */
static int8_t
rt2860_rssi2dbm(struct rt2860_softc *sc, uint8_t rssi, uint8_t rxpath)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211_channel *c;
	int chan;
	int8_t rssi_off, lna_gain;

	if (rssi == 0)
		return -99;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	c = ic->ic_curchan;
	chan = ieee80211_chan2ieee(ic, c);

	if (IEEE80211_IS_CHAN_5GHZ(c)) {
		rssi_off = sc->rssi_off_5ghz[rxpath];

		if (chan <= 64)
			lna_gain = sc->lna_gain[1];
		else if (chan <= 128)
			lna_gain = sc->lna_gain[2];
		else
			lna_gain = sc->lna_gain[3];
	} else {
		rssi_off = sc->rssi_off_2ghz[rxpath] - sc->lna_gain[0];
		lna_gain = sc->lna_gain[0];
	}

	return (-12 - rssi_off - lna_gain - rssi);
}

/*
 * rt2860_rate2mcs
 */
static uint8_t
rt2860_rate2mcs(uint8_t rate)
{
	switch (rate) {
	/* CCK rates */
	case 2:	return 0;
	case 4:	return 1;
	case 11: return 2;
	case 22: return 3;

	/* OFDM rates */
	case 12: return 0;
	case 18: return 1;
	case 24: return 2;
	case 36: return 3;
	case 48: return 4;
	case 72: return 5;
	case 96: return 6;
	case 108: return 7;
	}

	return 0;
}

/*
 * rt2860_tx_mgmt
 */
static int
rt2860_tx_mgmt(struct rt2860_softc *sc, struct mbuf *m,
    struct ieee80211_node *ni, int qid)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;
	const struct ieee80211_txparam *tp;
	struct rt2860_softc_node *rni;
	struct rt2860_softc_tx_ring *ring;
	struct rt2860_softc_tx_data *data;
	struct rt2860_txd *desc;
	struct rt2860_txwi *txwi;
	struct ieee80211_frame *wh;
	struct rt2860_softc_tx_radiotap_header *tap;
	bus_dma_segment_t dma_seg[RT2860_SOFTC_MAX_SCATTER];
	struct mbuf *m_d;
	u_int hdrsize, hdrspace;
	uint8_t rate, mcs, pid, qsel;
	uint16_t len, dmalen, mpdu_len, dur;
	int error, mimops, ndmasegs, ndescs, i, j;

	KASSERT(qid >= 0 && qid < RT2860_SOFTC_TX_RING_COUNT,
		("%s: Tx MGMT: invalid qid=%d\n",
		 device_get_nameunit(sc->sc_dev), qid));

	RT2860_SOFTC_TX_RING_ASSERT_LOCKED(&sc->tx_ring[qid]);

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = ni->ni_vap;
	rni = (struct rt2860_softc_node *) ni;
	tp = ni->ni_txparms;

	ring = &sc->tx_ring[qid];
	desc = &ring->desc[ring->desc_cur];
	data = &ring->data[ring->data_cur];
	txwi = (struct rt2860_txwi *) (ring->seg0 + ring->data_cur * RT2860_TX_DATA_SEG0_SIZE);

	wh = mtod(m, struct ieee80211_frame *);

	rate = tp->mgmtrate & IEEE80211_RATE_VAL;
	/* XXX */
	if (!rate)
		return EFBIG;

	/* fill Tx wireless info */
	if (ni->ni_flags & IEEE80211_NODE_HT)
		mcs = rate;
	else
		mcs = rt2860_rate2mcs(rate);

	/* calculate MPDU length without padding */

	hdrsize = ieee80211_anyhdrsize(wh);
	hdrspace = ieee80211_anyhdrspace(ic, wh);
	mpdu_len = m->m_pkthdr.len - hdrspace + hdrsize;

	memset(txwi, 0, sizeof(struct rt2860_txwi));

	/* management frames do not need encryption */

	txwi->wcid = RT2860_WCID_RESERVED;

	/* MIMO power save */
	if ((ni->ni_flags & IEEE80211_NODE_HT) && (ni->ni_flags & IEEE80211_NODE_MIMO_PS)) {
		if (mcs > 7) {
			if (ni->ni_flags & IEEE80211_NODE_MIMO_RTS) {
				/* dynamic MIMO power save */
				txwi->flags |= RT2860_TX_MMPS;
			} else {
				/* static MIMO power save */

				mcs = 7;
			}
		}

		mimops = 1;
	} else {
		mimops = 0;
	}

	pid = ((mcs < 0xf) ? (mcs + 1) : mcs) & 0xf;

	txwi->len = (htole16(pid) << RT2860_TX_PID_SHIFT) | htole16(mpdu_len);
	if (ni->ni_flags & IEEE80211_NODE_HT) {
		txwi->phy |= RT2860_PHY_HT;
	} else {
		if (ieee80211_rate2phytype(ic->ic_rt, rate) != IEEE80211_T_OFDM) {
			txwi->phy |= RT2860_PHY_CCK;

			if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE))
				mcs |= RT2860_PHY_SHPRE;
		} else {
			txwi->phy |= RT2860_PHY_OFDM;
		}
	}

	txwi->phy |= (mcs & RT2860_PHY_MCS);

	txwi->txop = RT2860_TX_TXOP_BACKOFF;

	/* skip ACKs for multicast frames */

	if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
		txwi->xflags |= RT2860_TX_ACK;

		if (ni->ni_flags & IEEE80211_NODE_HT) {
			/* preamble + plcp + signal extension + SIFS */

			dur = 16 + 4 + 6 + 10;
		} else {
			dur = ieee80211_ack_duration(ic->ic_rt, rate,
				ic->ic_flags & IEEE80211_F_SHPREAMBLE);
		}

		*(uint16_t *) wh->i_dur = htole16(dur);
	}

	/* ask MAC to insert timestamp into probe responses */

	if ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) ==
		(IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP))
		txwi->flags |= RT2860_TX_TS;

	if (ieee80211_radiotap_active_vap(vap)) {
		tap = &sc->txtap;

		tap->flags = IEEE80211_RADIOTAP_F_DATAPAD;
		tap->chan_flags = htole32(ic->ic_curchan->ic_flags);
		tap->chan_freq = htole16(ic->ic_curchan->ic_freq);
		tap->chan_ieee = ic->ic_curchan->ic_ieee;
		tap->chan_maxpow = 0;

		if (ni->ni_flags & IEEE80211_NODE_HT)
			tap->rate = mcs | IEEE80211_RATE_MCS;
		else
			tap->rate = rate;

		if (mcs & RT2860_PHY_SHPRE)
			tap->flags |= IEEE80211_RADIOTAP_F_SHORTPRE;

		if (wh->i_fc[1] & IEEE80211_FC1_WEP)
			tap->flags |= IEEE80211_RADIOTAP_F_WEP;

		if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
			wh->i_fc[1] &= ~IEEE80211_FC1_WEP;

			ieee80211_radiotap_tx(vap, m);

			wh->i_fc[1] |= IEEE80211_FC1_WEP;
		} else {
			ieee80211_radiotap_tx(vap, m);
		}
	}

	/* copy and trim 802.11 header */

	m_copydata(m, 0, hdrsize, (caddr_t) (txwi + 1));
	m_adj(m, hdrspace);

	error = bus_dmamap_load_mbuf_sg(ring->data_dma_tag, data->dma_map, m,
	    dma_seg, &ndmasegs, BUS_DMA_NOWAIT);
	if (error != 0) {
		/* too many fragments, linearize */

		RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
			"could not load mbuf DMA map, trying to linearize "
			"mbuf: ndmasegs=%d, len=%d, error=%d\n",
			 ndmasegs, m->m_pkthdr.len, error);

		m_d = m_collapse(m, M_DONTWAIT, 16);
		if (m_d == NULL) {
			RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
			    "m_collapse(m, M_DONTWAIT, 16) failed\n");
			m_freem(m);
			m = NULL;
			return (ENOMEM);
		}
		m = m_d;

		sc->tx_defrag_packets++;

		error = bus_dmamap_load_mbuf_sg(ring->data_dma_tag, data->dma_map, m,
	    	dma_seg, &ndmasegs, BUS_DMA_NOWAIT);
		if (error != 0) {
			device_printf(sc->sc_dev, "could not load mbuf DMA "
			    "map: ndmasegs=%d, len=%d, error=%d\n",
			    ndmasegs, m->m_pkthdr.len, error);
			m_freem(m);
			return error;
		}
	}

	if (m->m_pkthdr.len == 0)
		ndmasegs = 0;

	/* determine how many Tx descs are required */

	ndescs = 1 + ndmasegs / 2;
	if ((ring->desc_queued + ndescs) >
	    (RT2860_SOFTC_TX_RING_DESC_COUNT - 2)) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
		    "there are not enough Tx descs\n");

		sc->no_tx_desc_avail++;

		bus_dmamap_unload(ring->data_dma_tag, data->dma_map);
		m_freem(m);
		return EFBIG;
	}

	data->m = m;
	data->ni = ni;

	/* set up Tx descs */

	/* first segment is Tx wireless info and 802.11 header */

	len = sizeof(struct rt2860_txwi) + hdrsize;

	/* align end on a 4-bytes boundary */

	dmalen = (len + 3) & ~ 3;

	memset((caddr_t) txwi + len, 0, dmalen - len);

	qsel = RT2860_TX_QSEL_EDCA;

	desc->sdp0 = htole32(ring->seg0_phys_addr + ring->data_cur * RT2860_TX_DATA_SEG0_SIZE);
	desc->sdl0 = htole16(dmalen);
	desc->flags = (qsel << RT2860_TX_QSEL_SHIFT);

	/* set up payload segments */

	for (i = ndmasegs, j = 0; i >= 2; i -= 2) {
		desc->sdp1 = htole32(dma_seg[j].ds_addr);
		desc->sdl1 = htole16(dma_seg[j].ds_len);

		ring->desc_queued++;
		ring->desc_cur = (ring->desc_cur + 1) % RT2860_SOFTC_TX_RING_DESC_COUNT;

		j++;

		desc = &ring->desc[ring->desc_cur];

		desc->sdp0 = htole32(dma_seg[j].ds_addr);
		desc->sdl0 = htole16(dma_seg[j].ds_len);
		desc->flags = (qsel << RT2860_TX_QSEL_SHIFT);

		j++;
	}

	/* finalize last payload segment */

	if (i > 0) {
		desc->sdp1 = htole32(dma_seg[j].ds_addr);
		desc->sdl1 = htole16(dma_seg[j].ds_len | RT2860_TX_LS1);
	} else {
		desc->sdl0 |= htole16(RT2860_TX_LS0);
		desc->sdl1 = 0;
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
		"sending MGMT frame: qid=%d, hdrsize=%d, hdrspace=%d, len=%d, "
		"mcs=%d, mimops=%d, DMA len=%d, ndmasegs=%d, DMA ds_len=%d/%d/%d/%d/%d\n",
		qid, hdrsize, hdrspace, m->m_pkthdr.len + hdrsize,
		mcs, mimops, dmalen, ndmasegs,
		(int) dma_seg[0].ds_len, (int) dma_seg[1].ds_len, (int) dma_seg[2].ds_len, (int) dma_seg[3].ds_len, (int) dma_seg[4].ds_len);

	bus_dmamap_sync(ring->seg0_dma_tag, ring->seg0_dma_map,
		BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
		BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
		BUS_DMASYNC_PREWRITE);

	ring->desc_queued++;
	ring->desc_cur = (ring->desc_cur + 1) % RT2860_SOFTC_TX_RING_DESC_COUNT;

	ring->data_queued++;
	ring->data_cur = (ring->data_cur + 1) % RT2860_SOFTC_TX_RING_DATA_COUNT;

	/* kick Tx */

	RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_CTX_IDX(qid), ring->desc_cur);

	return 0;
}

/*
 * rt2860_tx_data
 */
static int
rt2860_tx_data(struct rt2860_softc *sc, struct mbuf *m,
    struct ieee80211_node *ni, int qid)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;
	const struct ieee80211_txparam *tp;
	struct rt2860_softc_node *rni;
	struct rt2860_softc_tx_ring *ring;
	struct rt2860_softc_tx_data *data;
	struct rt2860_txd *desc;
	struct rt2860_txwi *txwi;
	struct ieee80211_frame *wh;
	struct ieee80211_tx_ampdu *tx_ampdu;
	ieee80211_seq seqno;
	struct rt2860_softc_tx_radiotap_header *tap;
	bus_dma_segment_t dma_seg[RT2860_SOFTC_MAX_SCATTER];
	u_int hdrsize, hdrspace;
	uint8_t type, rate, stbc, shortgi, mcs, pid, wcid, mpdu_density, bawin_size, qsel;
	uint16_t qos, len, dmalen, mpdu_len, dur;
	int error, hasqos, ac, tid, ampdu, mimops, ndmasegs, ndescs, i, j;

	KASSERT(qid >= 0 && qid < RT2860_SOFTC_TX_RING_COUNT,
		("%s: Tx data: invalid qid=%d\n",
		 device_get_nameunit(sc->sc_dev), qid));

	RT2860_SOFTC_TX_RING_ASSERT_LOCKED(&sc->tx_ring[qid]);

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = ni->ni_vap;
	rni = (struct rt2860_softc_node *) ni;
	tp = ni->ni_txparms;

	ring = &sc->tx_ring[qid];
	desc = &ring->desc[ring->desc_cur];
	data = &ring->data[ring->data_cur];
	txwi = (struct rt2860_txwi *) (ring->seg0 + ring->data_cur * RT2860_TX_DATA_SEG0_SIZE);

	wh = mtod(m, struct ieee80211_frame *);

	type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;

	hasqos = IEEE80211_QOS_HAS_SEQ(wh);
	if (hasqos) {
		if (IEEE80211_HAS_ADDR4(wh))
			qos = le16toh(*(const uint16_t *)
				(((struct ieee80211_qosframe_addr4 *) wh)->i_qos));
		else
			qos = le16toh(*(const uint16_t *)
				(((struct ieee80211_qosframe *) wh)->i_qos));
	} else {
		qos = 0;
	}

	if (IEEE80211_IS_MULTICAST(wh->i_addr1))
		rate = tp->mcastrate;
	else if (m->m_flags & M_EAPOL)
		rate = tp->mgmtrate;
	else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
		rate = tp->ucastrate;
	else
		rate = ni->ni_txrate;

	rate &= IEEE80211_RATE_VAL;
/* XXX */
	if (!rate)
		return EFBIG;

	/* fill Tx wireless info */

	if (ni->ni_flags & IEEE80211_NODE_HT)
		mcs = rate;
	else
		mcs = rt2860_rate2mcs(rate);

	if (type == IEEE80211_FC0_TYPE_DATA)
		wcid = !IEEE80211_IS_MULTICAST(wh->i_addr1) ? rni->staid : RT2860_WCID_MCAST;
	else
		wcid = RT2860_WCID_RESERVED;

	/* calculate MPDU length without padding */

	hdrsize = ieee80211_anyhdrsize(wh);
	hdrspace = ieee80211_anyhdrspace(ic, wh);
	mpdu_len = m->m_pkthdr.len - hdrspace + hdrsize;

	memset(txwi, 0, sizeof(struct rt2860_txwi));

	txwi->wcid = wcid;

	/* MIMO power save */

	if ((ni->ni_flags & IEEE80211_NODE_HT) && (ni->ni_flags & IEEE80211_NODE_MIMO_PS)) {
		if (mcs > 7) {
			if (ni->ni_flags & IEEE80211_NODE_MIMO_RTS) {
				/* dynamic MIMO power save */

				txwi->flags |= RT2860_TX_MMPS;
			} else {
				/* static MIMO power save */

				mcs = 7;
			}
		}

		mimops = 1;
	} else {
		mimops = 0;
	}

	pid = ((mcs < 0xf) ? (mcs + 1) : mcs) & 0xf;

	txwi->len = (htole16(pid) << RT2860_TX_PID_SHIFT) | htole16(mpdu_len);

	stbc = sc->tx_stbc && (mcs <= 7) && (vap->iv_htcaps & IEEE80211_HTCAP_TXSTBC) &&
		(ni->ni_flags & IEEE80211_NODE_HT) && (ni->ni_htcap & IEEE80211_HTCAP_RXSTBC);

	shortgi = ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) && (ni->ni_flags & IEEE80211_NODE_SGI20) && (ni->ni_chw == 20)) ||
		((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) && (ni->ni_flags & IEEE80211_NODE_SGI40) && (ni->ni_chw == 40));

	txwi->phy |= (stbc << RT2860_PHY_STBC);
	if (shortgi)
		txwi->phy |= RT2860_PHY_SGI;

	if (ni->ni_flags & IEEE80211_NODE_HT) {
		txwi->phy |= RT2860_PHY_HT;
	} else {
		if (ieee80211_rate2phytype(ic->ic_rt, rate) != IEEE80211_T_OFDM) {
			txwi->phy |= RT2860_PHY_CCK;

			if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE))
				mcs |= RT2860_PHY_SHPRE;
		} else {
			txwi->phy |= RT2860_PHY_OFDM;
		}
	}

	if ((ni->ni_flags & IEEE80211_NODE_HT) && (ni->ni_chw == 40))
		txwi->phy |= RT2860_PHY_BW40;
	txwi->phy |= (mcs & RT2860_PHY_MCS);

	txwi->txop = RT2860_TX_TXOP_HT;

	if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
	    (!hasqos || (qos & IEEE80211_QOS_ACKPOLICY) !=
		IEEE80211_QOS_ACKPOLICY_NOACK)) {
		txwi->xflags |= RT2860_TX_ACK;

		if (ni->ni_flags & IEEE80211_NODE_HT) {
			/* preamble + plcp + signal extension + SIFS */
			dur = 16 + 4 + 6 + 10;
		} else {
			dur = ieee80211_ack_duration(ic->ic_rt, rate,
				ic->ic_flags & IEEE80211_F_SHPREAMBLE);
		}

		*(uint16_t *) wh->i_dur = htole16(dur);
	}

	/* check for A-MPDU */
	if (m->m_flags & M_AMPDU_MPDU) {
		ac = M_WME_GETAC(m);
		tid = WME_AC_TO_TID(ac);
		tx_ampdu = &ni->ni_tx_ampdu[ac];

		mpdu_density = RT2860_MS(ni->ni_htparam, IEEE80211_HTCAP_MPDUDENSITY);
		bawin_size = tx_ampdu->txa_wnd;

		txwi->flags |=
			(mpdu_density << RT2860_TX_MPDU_DSITY_SHIFT) | RT2860_TX_AMPDU;

		txwi->xflags |=
			(bawin_size << RT2860_TX_BAWINSIZE_SHIFT);

		seqno = ni->ni_txseqs[tid]++;

		*(uint16_t *) &wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT);

		ampdu = 1;
	} else {
		mpdu_density = 0;
		bawin_size = 0;
		ampdu = 0;
	}

	/* ask MAC to insert timestamp into probe responses */

	if ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) ==
		(IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP))
		txwi->flags |= RT2860_TX_TS;

	if (ieee80211_radiotap_active_vap(vap)) {
		tap = &sc->txtap;

		tap->flags = IEEE80211_RADIOTAP_F_DATAPAD;
		tap->chan_flags = htole32(ic->ic_curchan->ic_flags);
		tap->chan_freq = htole16(ic->ic_curchan->ic_freq);
		tap->chan_ieee = ic->ic_curchan->ic_ieee;
		tap->chan_maxpow = 0;

		if (ni->ni_flags & IEEE80211_NODE_HT)
			tap->rate = mcs | IEEE80211_RATE_MCS;
		else
			tap->rate = rate;

		if (mcs & RT2860_PHY_SHPRE)
			tap->flags |= IEEE80211_RADIOTAP_F_SHORTPRE;

		if (shortgi)
			tap->flags |= IEEE80211_RADIOTAP_F_SHORTGI;

		if (wh->i_fc[1] & IEEE80211_FC1_WEP)
			tap->flags |= IEEE80211_RADIOTAP_F_WEP;

		/* XXX use temporarily radiotap CFP flag as A-MPDU flag */

		if (ampdu)
			tap->flags |= IEEE80211_RADIOTAP_F_CFP;

		if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
			wh->i_fc[1] &= ~IEEE80211_FC1_WEP;

			ieee80211_radiotap_tx(vap, m);

			wh->i_fc[1] |= IEEE80211_FC1_WEP;
		} else {
			ieee80211_radiotap_tx(vap, m);
		}
	}

	/* copy and trim 802.11 header */

	m_copydata(m, 0, hdrsize, (caddr_t) (txwi + 1));
	m_adj(m, hdrspace);

	error = bus_dmamap_load_mbuf_sg(ring->data_dma_tag, data->dma_map, m,
	    dma_seg, &ndmasegs, BUS_DMA_NOWAIT);
	if (error != 0) {
		/* too many fragments, linearize */

		RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
		    "could not load mbuf DMA map, trying to linearize "
		    "mbuf: ndmasegs=%d, len=%d, error=%d\n",
		     ndmasegs,
		    m->m_pkthdr.len, error);

		m = m_defrag(m, M_DONTWAIT);
		if (m == NULL)
			return ENOMEM;

		sc->tx_defrag_packets++;

		error = bus_dmamap_load_mbuf_sg(ring->data_dma_tag,
		    data->dma_map, m, dma_seg, &ndmasegs, BUS_DMA_NOWAIT);
		if (error != 0) {
			device_printf(sc->sc_dev, "could not load mbuf DMA "
			    "map: ndmasegs=%d, len=%d, error=%d\n",
			    ndmasegs, m->m_pkthdr.len, error);
			m_freem(m);
			return error;
		}
	}

	if (m->m_pkthdr.len == 0)
		ndmasegs = 0;

	/* determine how many Tx descs are required */

	ndescs = 1 + ndmasegs / 2;
	if ((ring->desc_queued + ndescs) >
	    (RT2860_SOFTC_TX_RING_DESC_COUNT - 2)) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
		    "there are not enough Tx descs\n");

		sc->no_tx_desc_avail++;

		bus_dmamap_unload(ring->data_dma_tag, data->dma_map);
		m_freem(m);
		return EFBIG;
	}

	data->m = m;
	data->ni = ni;

	/* set up Tx descs */

	/* first segment is Tx wireless info and 802.11 header */

	len = sizeof(struct rt2860_txwi) + hdrsize;

	/* align end on a 4-bytes boundary */

	dmalen = (len + 3) & ~ 3;

	memset((caddr_t) txwi + len, 0, dmalen - len);

	qsel = RT2860_TX_QSEL_EDCA;

	desc->sdp0 = htole32(ring->seg0_phys_addr + ring->data_cur *
	    RT2860_TX_DATA_SEG0_SIZE);
	desc->sdl0 = htole16(dmalen);
	desc->flags = (qsel << RT2860_TX_QSEL_SHIFT);

	/* set up payload segments */

	for (i = ndmasegs, j = 0; i >= 2; i -= 2) {
		desc->sdp1 = htole32(dma_seg[j].ds_addr);
		desc->sdl1 = htole16(dma_seg[j].ds_len);

		ring->desc_queued++;
		ring->desc_cur = (ring->desc_cur + 1) %
		    RT2860_SOFTC_TX_RING_DESC_COUNT;

		j++;

		desc = &ring->desc[ring->desc_cur];

		desc->sdp0 = htole32(dma_seg[j].ds_addr);
		desc->sdl0 = htole16(dma_seg[j].ds_len);
		desc->flags = (qsel << RT2860_TX_QSEL_SHIFT);

		j++;
	}

	/* finalize last payload segment */

	if (i > 0) {
		desc->sdp1 = htole32(dma_seg[j].ds_addr);
		desc->sdl1 = htole16(dma_seg[j].ds_len | RT2860_TX_LS1);
	} else {
		desc->sdl0 |= htole16(RT2860_TX_LS0);
		desc->sdl1 = 0;
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
		"sending data: qid=%d, hdrsize=%d, hdrspace=%d, len=%d, "
		"bw=%d, stbc=%d, shortgi=%d, mcs=%d, wcid=0x%02x, "
		"ampdu=%d (density=%d, winsize=%d), mimops=%d, DMA len=%d, ndmasegs=%d, DMA ds_len=%d/%d/%d/%d/%d\n",
		qid, hdrsize, hdrspace, m->m_pkthdr.len + hdrsize,
		ni->ni_chw, stbc, shortgi, mcs, wcid, ampdu, mpdu_density, bawin_size, mimops, dmalen, ndmasegs,
		(int) dma_seg[0].ds_len, (int) dma_seg[1].ds_len, (int) dma_seg[2].ds_len, (int) dma_seg[3].ds_len, (int) dma_seg[4].ds_len);

	bus_dmamap_sync(ring->seg0_dma_tag, ring->seg0_dma_map,
		BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
		BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
		BUS_DMASYNC_PREWRITE);

	ring->desc_queued++;
	ring->desc_cur = (ring->desc_cur + 1) % RT2860_SOFTC_TX_RING_DESC_COUNT;

	ring->data_queued++;
	ring->data_cur = (ring->data_cur + 1) % RT2860_SOFTC_TX_RING_DATA_COUNT;

	/* kick Tx */

	RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_CTX_IDX(qid), ring->desc_cur);

	return 0;
}

/*
 * rt2860_tx_raw
static int
rt2860_tx_raw(struct rt2860_softc *sc, struct mbuf *m,
    struct ieee80211_node *ni,
    const struct ieee80211_bpf_params *params)
{
	RT2860_DPRINTF(sc, RT2860_DEBUG_TX, "Tx raw\n");

	return 0;
}
 */

/*
 * rt2860_intr
 */
void
rt2860_intr(void *arg)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	uint32_t status;

	sc = arg;
	ifp = sc->sc_ifp;

	/* acknowledge interrupts */

	status = RAL_READ(sc, RT2860_REG_SCHDMA_INT_STATUS);
	RAL_WRITE(sc, RT2860_REG_SCHDMA_INT_STATUS, status);

	RT2860_DPRINTF(sc, RT2860_DEBUG_INTR,
		"interrupt: status = 0x%08x\n",
		 status);

	if (status == 0xffffffff ||		/* device likely went away */
		status == 0)				/* not for us */
		return;

	sc->interrupts++;

	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
		return;

	if (status & RT2860_REG_INT_TX_COHERENT) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_INTR,
		    "Tx coherent interrupt\n");
		sc->tx_coherent_interrupts++;
		rt2860_reset_dma(sc);
	}

	if (status & RT2860_REG_INT_RX_COHERENT) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_INTR,
		    "Rx coherent interrupt\n");
		sc->rx_coherent_interrupts++;
		rt2860_reset_dma(sc);
	}

	if (status & RT2860_REG_INT_TXRX_COHERENT) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_INTR,
		    "Tx/Rx coherent interrupt\n");
		sc->txrx_coherent_interrupts++;
		rt2860_reset_dma(sc);
	}

	if (status & RT2860_REG_INT_FIFO_STA_FULL)
		rt2860_fifo_sta_full_intr(sc);

	if (status & RT2860_REG_INT_TX_MGMT_DONE)
		rt2860_tx_intr(sc, 5);

	if (status & RT2860_REG_INT_RX_DONE)
		rt2860_rx_intr(sc);

	if (status & RT2860_REG_INT_TX_HCCA_DONE)
		rt2860_tx_intr(sc, 4);

	if (status & RT2860_REG_INT_TX_AC3_DONE)
		rt2860_tx_intr(sc, 3);

	if (status & RT2860_REG_INT_TX_AC2_DONE)
		rt2860_tx_intr(sc, 2);

	if (status & RT2860_REG_INT_TX_AC1_DONE)
		rt2860_tx_intr(sc, 1);

	if (status & RT2860_REG_INT_TX_AC0_DONE)
		rt2860_tx_intr(sc, 0);
}

/*
 * rt2860_reset_dma
 */
static void
rt2860_reset_dma(struct rt2860_softc *sc)
{
	uint32_t tmp;
	int i;

	RT2860_DPRINTF(sc, RT2860_DEBUG_INTR, "Reseting DMA\n");
	RT2860_SOFTC_ASSERT_LOCKED(sc);

	/* restart DMA engine */
	tmp = RAL_READ(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG);
	tmp &= ~(RT2860_REG_TX_WB_DDONE |
		RT2860_REG_RX_DMA_ENABLE |
		RT2860_REG_TX_DMA_ENABLE);
	RAL_WRITE(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG, tmp);

	/* init Tx rings (4 EDCAs + HCCA + MGMT) */
	for (i = 0; i < RT2860_SOFTC_TX_RING_COUNT; i++)
		rt2860_reset_tx_ring(sc, &sc->tx_ring[i]);

	for (i = 0; i < RT2860_SOFTC_TX_RING_COUNT; i++) {
		RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_BASE_PTR(i),
			sc->tx_ring[i].desc_phys_addr);
		RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_MAX_CNT(i),
			RT2860_SOFTC_TX_RING_DESC_COUNT);
		RAL_WRITE(sc, RT2860_REG_SCHDMA_TX_CTX_IDX(i), 0);
	}

	/* init Rx ring */
	rt2860_reset_rx_ring(sc, &sc->rx_ring);

	RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_BASE_PTR,
		sc->rx_ring.desc_phys_addr);
	RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_MAX_CNT,
		RT2860_SOFTC_RX_RING_DATA_COUNT);
	RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_CALC_IDX,
		RT2860_SOFTC_RX_RING_DATA_COUNT - 1);

	rt2860_txrx_enable(sc);
}

/*
 * rt2860_fifo_sta_full_intr
 */
static void
rt2860_fifo_sta_full_intr(struct rt2860_softc *sc)
{

	RT2860_DPRINTF(sc, RT2860_DEBUG_INTR,
	    "FIFO statistic full interrupt\n");

	sc->fifo_sta_full_interrupts++;

	RT2860_SOFTC_LOCK(sc);

	if (!(sc->intr_disable_mask & RT2860_REG_INT_FIFO_STA_FULL)) {
		rt2860_intr_disable(sc, RT2860_REG_INT_FIFO_STA_FULL);

		taskqueue_enqueue(sc->taskqueue, &sc->fifo_sta_full_task);
	}

	sc->intr_pending_mask |= RT2860_REG_INT_FIFO_STA_FULL;

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_rx_intr
 */
static void
rt2860_rx_intr(struct rt2860_softc *sc)
{
	RT2860_DPRINTF(sc, RT2860_DEBUG_INTR, "Rx interrupt\n");

	sc->rx_interrupts++;

	RT2860_SOFTC_LOCK(sc);

	if (!(sc->intr_disable_mask & RT2860_REG_INT_RX_DONE)) {
		rt2860_intr_disable(sc, RT2860_REG_INT_RX_DONE);

		taskqueue_enqueue(sc->taskqueue, &sc->rx_done_task);
	}

	sc->intr_pending_mask |= RT2860_REG_INT_RX_DONE;

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_tx_intr
 */
static void
rt2860_tx_intr(struct rt2860_softc *sc, int qid)
{
	KASSERT(qid >= 0 && qid < RT2860_SOFTC_TX_RING_COUNT,
		("%s: Tx interrupt: invalid qid=%d\n",
		 device_get_nameunit(sc->sc_dev), qid));

	RT2860_DPRINTF(sc, RT2860_DEBUG_INTR,
		"Tx interrupt: qid=%d\n",
		 qid);

	sc->tx_interrupts[qid]++;

	RT2860_SOFTC_LOCK(sc);

	if (!(sc->intr_disable_mask & (RT2860_REG_INT_TX_AC0_DONE << qid))) {
		rt2860_intr_disable(sc, (RT2860_REG_INT_TX_AC0_DONE << qid));

		taskqueue_enqueue(sc->taskqueue, &sc->tx_done_task);
	}

	sc->intr_pending_mask |= (RT2860_REG_INT_TX_AC0_DONE << qid);

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_rx_done_task
 */
static void
rt2860_rx_done_task(void *context, int pending)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	int again;

	sc = context;
	ifp = sc->sc_ifp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_RX, "Rx done task\n");

	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
		return;

	sc->intr_pending_mask &= ~RT2860_REG_INT_RX_DONE;

	again = rt2860_rx_eof(sc, sc->rx_process_limit);

	RT2860_SOFTC_LOCK(sc);

	if ((sc->intr_pending_mask & RT2860_REG_INT_RX_DONE) || again) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
		    "Rx done task: scheduling again\n");

		taskqueue_enqueue(sc->taskqueue, &sc->rx_done_task);
	} else {
		rt2860_intr_enable(sc, RT2860_REG_INT_RX_DONE);
	}

	RT2860_SOFTC_UNLOCK(sc);
}

/*
 * rt2860_tx_done_task
 */
static void
rt2860_tx_done_task(void *context, int pending)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	uint32_t intr_mask;
	int i;

	sc = context;
	ifp = sc->sc_ifp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_TX, "Tx done task\n");

	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
		return;

	for (i = RT2860_SOFTC_TX_RING_COUNT - 1; i >= 0; i--) {
		if (sc->intr_pending_mask &
		    (RT2860_REG_INT_TX_AC0_DONE << i)) {

			sc->intr_pending_mask &=
			    ~(RT2860_REG_INT_TX_AC0_DONE << i);
			rt2860_tx_eof(sc, &sc->tx_ring[i]);
		}
	}

	sc->tx_timer = 0;

	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;

	intr_mask = (RT2860_REG_INT_TX_MGMT_DONE |
		RT2860_REG_INT_TX_HCCA_DONE |
		RT2860_REG_INT_TX_AC3_DONE |
		RT2860_REG_INT_TX_AC2_DONE |
		RT2860_REG_INT_TX_AC1_DONE |
		RT2860_REG_INT_TX_AC0_DONE);

	RT2860_SOFTC_LOCK(sc);

	rt2860_intr_enable(sc, ~sc->intr_pending_mask &
		(sc->intr_disable_mask & intr_mask));

	if (sc->intr_pending_mask & intr_mask) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
		    "Tx done task: scheduling again\n");

		taskqueue_enqueue(sc->taskqueue, &sc->tx_done_task);
	}

	RT2860_SOFTC_UNLOCK(sc);

	if (!IFQ_IS_EMPTY(&ifp->if_snd))
		rt2860_start(ifp);
}

/*
 * rt2860_fifo_sta_full_task
 */
static void
rt2860_fifo_sta_full_task(void *context, int pending)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;

	sc = context;
	ifp = sc->sc_ifp;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATS, "FIFO statistic full task\n");

	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
		return;

	sc->intr_pending_mask &= ~RT2860_REG_INT_FIFO_STA_FULL;

	rt2860_drain_fifo_stats(sc);

	RT2860_SOFTC_LOCK(sc);

	if (sc->intr_pending_mask & RT2860_REG_INT_FIFO_STA_FULL) {
		RT2860_DPRINTF(sc, RT2860_DEBUG_STATS,
		    "FIFO statistic full task: scheduling again\n");

		taskqueue_enqueue(sc->taskqueue, &sc->fifo_sta_full_task);
	} else {
		rt2860_intr_enable(sc, RT2860_REG_INT_FIFO_STA_FULL);
	}

	RT2860_SOFTC_UNLOCK(sc);

	if (!IFQ_IS_EMPTY(&ifp->if_snd))
		rt2860_start(ifp);
}

/*
 * rt2860_periodic_task
 */
static void
rt2860_periodic_task(void *context, int pending)
{
	struct rt2860_softc *sc;
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;

	sc = context;
	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = TAILQ_FIRST(&ic->ic_vaps);

	RT2860_DPRINTF(sc, RT2860_DEBUG_PERIODIC,
		"periodic task: round=%lu\n",
		 sc->periodic_round);

	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
		return;

	RT2860_SOFTC_LOCK(sc);

	sc->periodic_round++;

	rt2860_update_stats(sc);

	if ((sc->periodic_round % 10) == 0) {
		rt2860_bbp_tuning(sc);

		rt2860_update_raw_counters(sc);

		rt2860_watchdog(sc);

		if (vap != NULL && vap->iv_opmode != IEEE80211_M_MONITOR &&
		    vap->iv_state == IEEE80211_S_RUN) {
			if (vap->iv_opmode == IEEE80211_M_STA)
				rt2860_amrr_update_iter_func(vap,
				    vap->iv_bss);
			else
				ieee80211_iterate_nodes(&ic->ic_sta,
				    rt2860_amrr_update_iter_func, vap);
		}
	}

	RT2860_SOFTC_UNLOCK(sc);

	callout_reset(&sc->periodic_ch, hz / 10, rt2860_periodic, sc);
}

/*
 * rt2860_rx_eof
 */
static int
rt2860_rx_eof(struct rt2860_softc *sc, int limit)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211_frame *wh;
	struct ieee80211_node *ni;
	struct rt2860_softc_node *rni;
	struct rt2860_softc_rx_radiotap_header *tap;
	struct rt2860_softc_rx_ring *ring;
	struct rt2860_rxd *desc;
	struct rt2860_softc_rx_data *data;
	struct rt2860_rxwi *rxwi;
	struct mbuf *m, *mnew;
	bus_dma_segment_t segs[1];
	bus_dmamap_t dma_map;
	uint32_t index, desc_flags;
	uint8_t rssi, ant, phymode, bw, shortgi, stbc, mcs, tid, frag;
#ifndef RT2860_NO_HW_CRYPTO
	uint8_t cipher_err, keyidx;
#endif
	uint16_t seq;
	int8_t rssi_dbm;
	int error, nsegs, len, ampdu, amsdu, rssi_dbm_rel, nframes, i;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	ring = &sc->rx_ring;

	nframes = 0;

	while (limit != 0) {
		index = RAL_READ(sc, RT2860_REG_SCHDMA_RX_DRX_IDX);
		if (ring->cur == index)
			break;

		desc = &ring->desc[ring->cur];
		data = &ring->data[ring->cur];

		bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
			BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
#ifdef XXX_TESTED_AND_WORKED
		if (!(desc->sdl0 & htole16(RT2860_RX_DDONE)))
			break;
#endif

		nframes++;

		mnew = m_getjcl(M_DONTWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE);
		if (mnew == NULL) {
			sc->rx_mbuf_alloc_errors++;
			ifp->if_ierrors++;
			goto skip;
		}

		mnew->m_len = mnew->m_pkthdr.len = MJUMPAGESIZE;

		error = bus_dmamap_load_mbuf_sg(ring->data_dma_tag, ring->spare_dma_map,
			mnew, segs, &nsegs, BUS_DMA_NOWAIT);
		if (error != 0) {
			RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
				"could not load Rx mbuf DMA map: error=%d, nsegs=%d\n",
				 error, nsegs);

			m_freem(mnew);

			sc->rx_mbuf_dmamap_errors++;
			ifp->if_ierrors++;

			goto skip;
		}

		KASSERT(nsegs == 1, ("%s: too many DMA segments",
			device_get_nameunit(sc->sc_dev)));

		bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
			BUS_DMASYNC_POSTREAD);
		bus_dmamap_unload(ring->data_dma_tag, data->dma_map);

		dma_map = data->dma_map;
		data->dma_map = ring->spare_dma_map;
		ring->spare_dma_map = dma_map;

		bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
			BUS_DMASYNC_PREREAD);

		m = data->m;

		data->m = mnew;
		uint32_t *tmp = (uint32_t *)desc;
		RT2860_DPRINTF(sc, RT2860_DEBUG_RX, "rxdesc 0x%08x 0x%08x 0x%08x 0x%08x\n", tmp[0], tmp[1], tmp[2], tmp[3]);
		desc->sdp0 = htole32(segs[0].ds_addr);

		desc_flags = le32toh(desc->flags);

		RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
			"Rx frame: rxdesc flags=0x%08x\n",
			 desc_flags);

		/* get Rx wireless info */

		rxwi = mtod(m, struct rt2860_rxwi *);
		tmp = (uint32_t *)rxwi;
		RT2860_DPRINTF(sc, RT2860_DEBUG_RX, "rxdesc 0x%08x 0x%08x 0x%08x 0x%08x\n", tmp[0], tmp[1], tmp[2], tmp[3]);
		len = (le16toh(rxwi->len) >> RT2860_RXWI_SIZE_SHIFT) &
			RT2860_RXWI_SIZE_MASK;

		/* check for L2 padding between IEEE 802.11 frame header and body */

		if (desc_flags & RT2860_RX_L2PAD) {
			RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
				"L2 padding: len=%d\n",
				 len);

			len += 2;
		}

		m->m_pkthdr.rcvif = ifp;
		m->m_data = (caddr_t) (rxwi + 1);
		m->m_pkthdr.len = m->m_len = len;

		/* check for crc errors */

		if (desc_flags & RT2860_RX_CRCERR) {
			RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
			    "rxdesc: crc error\n");

			ifp->if_ierrors++;

			if (!(ifp->if_flags & IFF_PROMISC)) {
				m_freem(m);
				goto skip;
			}
		}

		wh = (struct ieee80211_frame *) (rxwi + 1);

		/* check for cipher errors */
#ifndef RT2860_NO_HW_CRYPTO
		if (desc_flags & RT2860_RX_DEC) {
			cipher_err = ((desc_flags >>
			    RT2860_RX_CIPHER_ERR_SHIFT) &
			    RT2860_RX_CIPHER_ERR_MASK);
			if (cipher_err ==
			    RT2860_RX_CIPHER_ERR_NONE) {
				if (wh->i_fc[1] & IEEE80211_FC1_WEP)
					wh->i_fc[1] &= ~IEEE80211_FC1_WEP;

				m->m_flags |= M_WEP;

				sc->rx_cipher_no_errors++;
			} else {
				RT2860_DPRINTF(sc, RT2860_DEBUG_RX_CRYPT,
				    "rxdesc: cipher error=0x%02x "
				    "keyidx=%d\n",
				    cipher_err,
				    (rxwi->keyidx >>
					RT2860_RXWI_KEYIDX_SHIFT) &
				    RT2860_RXWI_KEYIDX_MASK);

				if (cipher_err ==
				    RT2860_RX_CIPHER_ERR_ICV)
					sc->rx_cipher_icv_errors++;
				else if (cipher_err ==
				    RT2860_RX_CIPHER_ERR_MIC)
					sc->rx_cipher_mic_errors++;
				else if (cipher_err ==
				    RT2860_RX_CIPHER_ERR_INVALID_KEY)
					sc->rx_cipher_invalid_key_errors++;

				/* XXX style(9) */
				if ((cipher_err == RT2860_RX_CIPHER_ERR_MIC) && (desc_flags & RT2860_RX_MYBSS)) {
					ni = ieee80211_find_rxnode(ic, (const struct ieee80211_frame_min *) wh);
					if (ni != NULL)
					{
						keyidx = (rxwi->keyidx >> RT2860_RXWI_KEYIDX_SHIFT) & RT2860_RXWI_KEYIDX_MASK;

						ieee80211_notify_michael_failure(ni->ni_vap, wh, keyidx);

						ieee80211_free_node(ni);
					}
				}

				ifp->if_ierrors++;

				if (!(ifp->if_flags & IFF_PROMISC)) {
					m_free(m);
					goto skip;
				}
			}
		} else {
			if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
				RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
				    "rxdesc: not decrypted but "
				    "protected flag set\n");

				ifp->if_ierrors++;

				if (!(ifp->if_flags & IFF_PROMISC)) {
					m_free(m);
					goto skip;
				}
			}
		}
#endif
		/* check for A-MPDU */

		if (desc_flags & RT2860_RX_BA) {
			m->m_flags |= M_AMPDU;

			sc->rx_ampdu++;

			if (wh->i_fc[1] & IEEE80211_FC1_RETRY)
				sc->rx_ampdu_retries++;

			ampdu = 1;
		} else {
			ampdu = 0;
		}

		/* check for A-MSDU */

		if (desc_flags & RT2860_RX_AMSDU) {
			sc->rx_amsdu++;

			amsdu = 1;
		} else {
			amsdu = 0;
		}

		ant = rt2860_maxrssi_rxpath(sc, rxwi);
		rssi = rxwi->rssi[ant];
		rssi_dbm = rt2860_rssi2dbm(sc, rssi, ant);
		phymode = ((rxwi->phymode_stbc_shortgi >> RT2860_RXWI_PHYMODE_SHIFT) & RT2860_RXWI_PHYMODE_MASK);
		bw = ((rxwi->bw_mcs >> RT2860_RXWI_BW_SHIFT) & RT2860_RXWI_BW_MASK);
		shortgi = ((rxwi->phymode_stbc_shortgi >> RT2860_RXWI_SHORTGI_SHIFT) &
			RT2860_RXWI_SHORTGI_MASK);
		stbc = ((rxwi->phymode_stbc_shortgi >> RT2860_RXWI_STBC_SHIFT) &
			RT2860_RXWI_STBC_MASK);
		mcs = ((rxwi->bw_mcs >> RT2860_RXWI_MCS_SHIFT) & RT2860_RXWI_MCS_MASK);
		tid = ((rxwi->len >> RT2860_RX_TID_SHIFT) & RT2860_RXWI_TID_MASK);
		seq = ((rxwi->seq >> RT2860_RXWI_SEQ_SHIFT) & RT2860_RXWI_SEQ_MASK);
		frag = ((rxwi->seq >> RT2860_RXWI_FRAG_SHIFT) & RT2860_RXWI_FRAG_MASK);

		if (ieee80211_radiotap_active(ic)) {
			tap = &sc->rxtap;

			tap->flags = (desc_flags & RT2860_RX_L2PAD) ? IEEE80211_RADIOTAP_F_DATAPAD : 0;
			tap->dbm_antsignal = rssi_dbm;
			tap->dbm_antnoise = RT2860_NOISE_FLOOR;
			tap->antenna = ant;
			tap->antsignal = rssi;
			tap->chan_flags = htole32(ic->ic_curchan->ic_flags);
			tap->chan_freq = htole16(ic->ic_curchan->ic_freq);
			tap->chan_ieee = ic->ic_curchan->ic_ieee;
			tap->chan_maxpow = 0;

			if (phymode == RT2860_RXWI_PHYMODE_HT_MIXED || phymode == RT2860_RXWI_PHYMODE_HT_GF)
				tap->rate = mcs | IEEE80211_RATE_MCS;
			else
				tap->rate = rt2860_rxrate(rxwi);

			if (desc_flags & RT2860_RX_CRCERR)
				tap->flags |= IEEE80211_RADIOTAP_F_BADFCS;

			if (desc_flags & RT2860_RX_FRAG)
				tap->flags |= IEEE80211_RADIOTAP_F_FRAG;

			if (rxwi->bw_mcs & RT2860_RXWI_MCS_SHOTPRE)
				tap->flags |= IEEE80211_RADIOTAP_F_SHORTPRE;

			if ((desc_flags & RT2860_RX_DEC) ||
				(wh->i_fc[1] & IEEE80211_FC1_WEP))
				tap->flags |= IEEE80211_RADIOTAP_F_WEP;

			if (shortgi)
				tap->flags |= IEEE80211_RADIOTAP_F_SHORTGI;

			/* XXX use temporarily radiotap CFP flag as A-MPDU flag */

			if (ampdu)
				tap->flags |= IEEE80211_RADIOTAP_F_CFP;
		}

		/*
		 * net80211 assumes that RSSI data are in the range [-127..127] and
		 * in .5 dBm units relative to the current noise floor
		 */

		rssi_dbm_rel = (rssi_dbm - RT2860_NOISE_FLOOR) * 2;
		if (rssi_dbm_rel > 127)
			rssi_dbm_rel = 127;

		RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
			"received frame: len=%d, phymode=%d, bw=%d, "
			"shortgi=%d, stbc=0x%02x, mcs=%d, "
			"ant=%d, rssi=%d/%d/%d, snr=%d/%d, wcid=0x%02x, "
			"ampdu=%d, amsdu=%d, tid=%d, seq=%d, frag=%d, "
			"retry=%d, rssi_dbm=%d, rssi_dbm_rel=%d\n",
			len, phymode, bw, shortgi, stbc, mcs,
			ant, rxwi->rssi[0], rxwi->rssi[1], rxwi->rssi[2],
			rxwi->snr[0], rxwi->snr[1],
			rxwi->wcid, ampdu, amsdu, tid, seq, frag,
			(wh->i_fc[1] & IEEE80211_FC1_RETRY) ? 1 : 0,
			rssi_dbm, rssi_dbm_rel);

		ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *) wh);
		if (ni != NULL) {
			rni = (struct rt2860_softc_node *) ni;

			for (i = 0; i < RT2860_SOFTC_RSSI_COUNT; i++) {
				rni->last_rssi[i] = rxwi->rssi[i];
				rni->last_rssi_dbm[i] = rt2860_rssi2dbm(sc, rxwi->rssi[i], i);
			}

			ieee80211_input(ni, m, rssi_dbm_rel, RT2860_NOISE_FLOOR);
			ieee80211_free_node(ni);
		} else {
			ieee80211_input_all(ic, m, rssi_dbm_rel, RT2860_NOISE_FLOOR);
		}

skip:

		desc->sdl0 &= ~htole16(RT2860_RX_DDONE);

		bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
			BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

		ring->cur = (ring->cur + 1) % RT2860_SOFTC_RX_RING_DATA_COUNT;

		limit--;
	}

	if (ring->cur == 0)
		RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_CALC_IDX,
			RT2860_SOFTC_RX_RING_DATA_COUNT - 1);
	else
		RAL_WRITE(sc, RT2860_REG_SCHDMA_RX_CALC_IDX,
			ring->cur - 1);

	RT2860_DPRINTF(sc, RT2860_DEBUG_RX,
		"Rx eof: nframes=%d\n",
		 nframes);

	sc->rx_packets += nframes;

	return (limit == 0);
}

/*
 * rt2860_tx_eof
 */
static void
rt2860_tx_eof(struct rt2860_softc *sc, struct rt2860_softc_tx_ring *ring)
{
	struct ifnet *ifp;
	struct rt2860_txd *desc;
	struct rt2860_softc_tx_data *data;
	uint32_t index;
	int ndescs, nframes;

	ifp = sc->sc_ifp;

	ndescs = 0;
	nframes = 0;

	for (;;) {
		index = RAL_READ(sc,
		    RT2860_REG_SCHDMA_TX_DTX_IDX(ring->qid));
		if (ring->desc_next == index)
			break;

		ndescs++;

		rt2860_drain_fifo_stats(sc);

		desc = &ring->desc[ring->desc_next];

		bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
			BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);

		if (desc->sdl0 & htole16(RT2860_TX_LS0) ||
			desc->sdl1 & htole16(RT2860_TX_LS1)) {
			nframes++;

			data = &ring->data[ring->data_next];

			if (data->m->m_flags & M_TXCB)
				ieee80211_process_callback(data->ni, data->m, 0);

			bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
				BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(ring->data_dma_tag, data->dma_map);

			m_freem(data->m);

			ieee80211_free_node(data->ni);

			data->m = NULL;
			data->ni = NULL;

			ifp->if_opackets++;

			RT2860_SOFTC_TX_RING_LOCK(ring);

			ring->data_queued--;
			ring->data_next = (ring->data_next + 1) % RT2860_SOFTC_TX_RING_DATA_COUNT;

			RT2860_SOFTC_TX_RING_UNLOCK(ring);
		}

		desc->sdl0 &= ~htole16(RT2860_TX_DDONE);

		bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
			BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

		RT2860_SOFTC_TX_RING_LOCK(ring);

		ring->desc_queued--;
		ring->desc_next = (ring->desc_next + 1) %
		    RT2860_SOFTC_TX_RING_DESC_COUNT;

		RT2860_SOFTC_TX_RING_UNLOCK(ring);
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_TX,
		"Tx eof: qid=%d, ndescs=%d, nframes=%d\n",
		 ring->qid, ndescs, nframes);
}

/*
 * rt2860_update_stats
 */
static void
rt2860_update_stats(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	uint32_t stacnt[3];
	int beacons, noretryok, retryok, failed, underflows, zerolen;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATS, "update statistic\n");

	rt2860_drain_fifo_stats(sc);

	/* read and clear Tx statistic registers */
	stacnt[0] = le32toh(RAL_READ(sc, RT2860_REG_TX_STA_CNT0));
	stacnt[1] = le32toh(RAL_READ(sc, RT2860_REG_TX_STA_CNT1));
	stacnt[2] = le32toh(RAL_READ(sc, RT2860_REG_TX_STA_CNT2));

	beacons = stacnt[0] >> 16;
	noretryok = stacnt[1] & 0xffff;
	retryok = stacnt[1] >> 16;
	failed = stacnt[0] & 0xffff;
	underflows = stacnt[2] >> 16;
	zerolen = stacnt[2] & 0xffff;

	RT2860_DPRINTF(sc, RT2860_DEBUG_STATS,
		"update statistic: beacons=%d, noretryok=%d, retryok=%d, failed=%d, underflows=%d, zerolen=%d\n",
		beacons, noretryok, retryok, failed, underflows, zerolen);

	ifp->if_oerrors += failed;

	sc->tx_beacons += beacons;
	sc->tx_noretryok += noretryok;
	sc->tx_retryok += retryok;
	sc->tx_failed += failed;
	sc->tx_underflows += underflows;
	sc->tx_zerolen += zerolen;
}

/*
 * rt2860_bbp_tuning
 */
static void
rt2860_bbp_tuning(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	struct ieee80211vap *vap;
	struct ieee80211_node *ni;
	int chan, group;
	int8_t rssi, old, new;

	/* RT2860C does not support BBP tuning */

	if (sc->mac_rev == 0x28600100)
		return;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;
	vap = TAILQ_FIRST(&ic->ic_vaps);

	if ((ic->ic_flags & IEEE80211_F_SCAN) || vap == NULL ||
		vap->iv_opmode != IEEE80211_M_STA || vap->iv_state != IEEE80211_S_RUN)
		return;

	ni = vap->iv_bss;

	chan = ieee80211_chan2ieee(ic, ni->ni_chan);

	if (chan <= 14)
		group = 0;
	else if (chan <= 64)
		group = 1;
	else if (chan <= 128)
		group = 2;
	else
		group = 3;

	rssi = ieee80211_getrssi(vap);

	if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) {
		new = 0x2e + sc->lna_gain[group];
	} else {
		if (!IEEE80211_IS_CHAN_HT40(ni->ni_chan))
			new = 0x32 + sc->lna_gain[group] * 5 / 3;
		else
			new = 0x3a + sc->lna_gain[group] * 5 / 3;
	}

	/* Tune if absolute average RSSI is greater than -80 */

	if (rssi > 30)
		new += 0x10;

	old = rt2860_io_bbp_read(sc, 66);

	if (old != new)
		rt2860_io_bbp_write(sc, 66, new);
}

/*
 * rt2860_watchdog
 */
static void
rt2860_watchdog(struct rt2860_softc *sc)
{
	uint32_t tmp;
	int ntries;

	tmp = RAL_READ(sc, RT2860_REG_PBF_TXRXQ_PCNT);

	RT2860_DPRINTF(sc, RT2860_DEBUG_WATCHDOG,
		"watchdog: TXRXQ_PCNT=0x%08x\n",
		 tmp);

	if (((tmp >> RT2860_REG_TX0Q_PCNT_SHIFT) &
	    RT2860_REG_TX0Q_PCNT_MASK) != 0) {
		sc->tx_queue_not_empty[0]++;

		RAL_WRITE(sc, RT2860_REG_PBF_CFG, 0xf40012);

		for (ntries = 0; ntries < 10; ntries++) {
			tmp = RAL_READ(sc,
			    RT2860_REG_PBF_TXRXQ_PCNT);

			if (((tmp >> RT2860_REG_TX0Q_PCNT_SHIFT) &
			    RT2860_REG_TX0Q_PCNT_MASK) == 0)
				break;

			DELAY(1);
		}

		RAL_WRITE(sc, RT2860_REG_PBF_CFG, 0xf40006);
	}

	if (((tmp >> RT2860_REG_TX1Q_PCNT_SHIFT) &
	    RT2860_REG_TX1Q_PCNT_MASK) != 0) {
		sc->tx_queue_not_empty[1]++;

		RAL_WRITE(sc, RT2860_REG_PBF_CFG, 0xf4000a);

		for (ntries = 0; ntries < 10; ntries++) {
			tmp = RAL_READ(sc, RT2860_REG_PBF_TXRXQ_PCNT);
			if (((tmp >> RT2860_REG_TX1Q_PCNT_SHIFT) &
			    RT2860_REG_TX1Q_PCNT_MASK) == 0)
				break;

			DELAY(1);
		}

		RAL_WRITE(sc, RT2860_REG_PBF_CFG, 0xf40006);
	}
}

/*
 * rt2860_drain_fifo_stats
 */
static void
rt2860_drain_fifo_stats(struct rt2860_softc *sc)
{
	struct ifnet *ifp;
	uint32_t stats;
	uint8_t wcid, mcs, pid;
	int ok, agg, retrycnt;

	ifp = sc->sc_ifp;

	/* drain Tx status FIFO (maxsize = 16) */

	while ((stats = RAL_READ(sc, RT2860_REG_TX_STA_FIFO)) &
		RT2860_TXQ_VLD) {

		wcid = ((stats & RT2860_TXQ_WCID_MASK ) >> RT2860_TXQ_WCID_SHIFT);

		/* if no ACK was requested, no feedback is available */

		if (!(stats & RT2860_TXQ_ACKREQ) || wcid == RT2860_WCID_RESERVED)
			continue;

		/* update AMRR statistic */

		ok = (stats & RT2860_TXQ_OK) ? 1 : 0;
		agg = (stats & RT2860_TXQ_AGG) ? 1 : 0;
		mcs = (stats >> RT2860_TXQ_MCS_SHIFT);
		pid = (stats >> RT2860_TXQ_PID_SHIFT);
		retrycnt = (mcs < 0xf) ? (pid - mcs - 1) : 0;

		RT2860_DPRINTF(sc, RT2860_DEBUG_STATS,
			"FIFO statistic: wcid=0x%02x, ok=%d, agg=%d, mcs=0x%02x, pid=0x%02x, retrycnt=%d\n",
			wcid, ok, agg, mcs, pid, retrycnt);

		rt2860_amrr_tx_complete(&sc->amrr_node[wcid], ok, retrycnt);

		if (!ok)
			ifp->if_oerrors++;
	}
}

/*
 * rt2860_update_raw_counters
 */
static void
rt2860_update_raw_counters(struct rt2860_softc *sc)
{
	uint32_t tmp;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT);

	sc->tx_nonagg += tmp & 0xffff;
	sc->tx_agg += tmp >> 16;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT0);

	sc->tx_ampdu += (tmp & 0xffff) / 1 + (tmp >> 16) / 2;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT1);

	sc->tx_ampdu += (tmp & 0xffff) / 3 + (tmp >> 16) / 4;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT2);

	sc->tx_ampdu += (tmp & 0xffff) / 5 + (tmp >> 16) / 6;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT3);

	sc->tx_ampdu += (tmp & 0xffff) / 7 + (tmp >> 16) / 8;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT4);

	sc->tx_ampdu += (tmp & 0xffff) / 9 + (tmp >> 16) / 10;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT5);

	sc->tx_ampdu += (tmp & 0xffff) / 11 + (tmp >> 16) / 12;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT6);

	sc->tx_ampdu += (tmp & 0xffff) / 13 + (tmp >> 16) / 14;

	tmp = RAL_READ(sc, RT2860_REG_TX_AGG_CNT7);

	sc->tx_ampdu += (tmp & 0xffff) / 15 + (tmp >> 16) / 16;

	tmp = RAL_READ(sc, RT2860_REG_RX_STA_CNT0);

	sc->rx_crc_errors += tmp & 0xffff;
	sc->rx_phy_errors += tmp >> 16;

	tmp = RAL_READ(sc, RT2860_REG_RX_STA_CNT1);

	sc->rx_false_ccas += tmp & 0xffff;
	sc->rx_plcp_errors += tmp >> 16;

	tmp = RAL_READ(sc, RT2860_REG_RX_STA_CNT2);

	sc->rx_dup_packets += tmp & 0xffff;
	sc->rx_fifo_overflows += tmp >> 16;

	tmp = RAL_READ(sc, RT2860_REG_TXRX_MPDU_DEN_CNT);

	sc->tx_mpdu_zero_density += tmp & 0xffff;
	sc->rx_mpdu_zero_density += tmp >> 16;
}

/*
 * rt2860_intr_enable
 */
static void
rt2860_intr_enable(struct rt2860_softc *sc, uint32_t intr_mask)
{
	uint32_t tmp;

	sc->intr_disable_mask &= ~intr_mask;

	tmp = sc->intr_enable_mask & ~sc->intr_disable_mask;

	RAL_WRITE(sc, RT2860_REG_SCHDMA_INT_MASK, tmp);
}

/*
 * rt2860_intr_disable
 */
static void
rt2860_intr_disable(struct rt2860_softc *sc, uint32_t intr_mask)
{
	uint32_t tmp;

	sc->intr_disable_mask |= intr_mask;

	tmp = sc->intr_enable_mask & ~sc->intr_disable_mask;

	RAL_WRITE(sc, RT2860_REG_SCHDMA_INT_MASK, tmp);
}

/*
 * rt2860_txrx_enable
 */
static int
rt2860_txrx_enable(struct rt2860_softc *sc)
{
	struct ieee80211com *ic;
	struct ifnet *ifp;
	uint32_t tmp;
	int ntries;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	/* enable Tx/Rx DMA engine */

	RAL_WRITE(sc, RT2860_MAC_SYS_CTRL, RT2860_REG_TX_ENABLE);

	for (ntries = 0; ntries < 200; ntries++) {
		tmp = RAL_READ(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG);
		if (!(tmp & (RT2860_REG_TX_DMA_BUSY | RT2860_REG_RX_DMA_BUSY)))
			break;

		DELAY(1000);
	}

	if (ntries == 200) {
		device_printf(sc->sc_dev, "timeout waiting for DMA engine\n");
		return -1;
	}

	DELAY(50);

	tmp |= RT2860_REG_TX_WB_DDONE |
		RT2860_REG_RX_DMA_ENABLE |
		RT2860_REG_TX_DMA_ENABLE |
		(RT2860_REG_WPDMA_BT_SIZE64 << RT2860_REG_WPDMA_BT_SIZE_SHIFT);

	RAL_WRITE(sc, RT2860_REG_SCHDMA_WPDMA_GLO_CFG, tmp);

	/* set Rx filter */

	tmp = RT2860_DROP_CRC_ERR |
		RT2860_DROP_PHY_ERR;

	if (ic->ic_opmode != IEEE80211_M_MONITOR) {
		tmp |= RT2860_DROP_DUPL |
			RT2860_DROP_CTS |
			RT2860_DROP_BA |
			RT2860_DROP_ACK |
			RT2860_DROP_VER_ERR |
			RT2860_DROP_CTRL_RSV |
			RT2860_DROP_CFACK |
			RT2860_DROP_CFEND;

		if (ic->ic_opmode == IEEE80211_M_STA)
			tmp |= RT2860_DROP_RTS |
				RT2860_DROP_PSPOLL;

		if (!(ifp->if_flags & IFF_PROMISC))
			tmp |= RT2860_DROP_UC_NOME;
	}

	RAL_WRITE(sc, RT2860_REG_RX_FILTER_CFG, tmp);

	RAL_WRITE(sc, RT2860_MAC_SYS_CTRL,
		RT2860_REG_RX_ENABLE | RT2860_REG_TX_ENABLE);

	return 0;
}

/*
 * rt2860_alloc_rx_ring
 */
static int
rt2860_alloc_rx_ring(struct rt2860_softc *sc,
    struct rt2860_softc_rx_ring *ring)
{
	struct rt2860_rxd *desc;
	struct rt2860_softc_rx_data *data;
	bus_dma_segment_t segs[1];
	int i, nsegs, error;

	error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), PAGE_SIZE, 0,
	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
	    RT2860_SOFTC_RX_RING_DATA_COUNT * sizeof(struct rt2860_rxd), 1,
	    RT2860_SOFTC_RX_RING_DATA_COUNT * sizeof(struct rt2860_rxd),
	    0, NULL, NULL, &ring->desc_dma_tag);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not create Rx desc DMA tag\n");
		goto fail;
	}

	error = bus_dmamem_alloc(ring->desc_dma_tag, (void **) &ring->desc,
	    BUS_DMA_NOWAIT | BUS_DMA_ZERO, &ring->desc_dma_map);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not allocate Rx desc DMA memory\n");
		goto fail;
	}

	error = bus_dmamap_load(ring->desc_dma_tag, ring->desc_dma_map,
	    ring->desc,
	    RT2860_SOFTC_RX_RING_DATA_COUNT * sizeof(struct rt2860_rxd),
	    rt2860_dma_map_addr, &ring->desc_phys_addr, 0);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not load Rx desc DMA map\n");
		goto fail;
	}

	error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), PAGE_SIZE, 0,
	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
	    MJUMPAGESIZE, 1, MJUMPAGESIZE, 0, NULL, NULL,
	    &ring->data_dma_tag);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not create Rx data DMA tag\n");
		goto fail;
	}

	for (i = 0; i < RT2860_SOFTC_RX_RING_DATA_COUNT; i++) {
		desc = &ring->desc[i];
		data = &ring->data[i];

		error = bus_dmamap_create(ring->data_dma_tag, 0,
		    &data->dma_map);
		if (error != 0) {
			device_printf(sc->sc_dev,
			    "could not create Rx data DMA map\n");
			goto fail;
		}

		data->m = m_getjcl(M_DONTWAIT, MT_DATA, M_PKTHDR,
		    MJUMPAGESIZE);
		if (data->m == NULL) {
			device_printf(sc->sc_dev,
			    "could not allocate Rx mbuf\n");
			error = ENOMEM;
			goto fail;
		}

		data->m->m_len = data->m->m_pkthdr.len = MJUMPAGESIZE;

		error = bus_dmamap_load_mbuf_sg(ring->data_dma_tag,
		    data->dma_map, data->m, segs, &nsegs, BUS_DMA_NOWAIT);
		if (error != 0) {
			device_printf(sc->sc_dev,
			    "could not load Rx mbuf DMA map\n");
			goto fail;
		}

		KASSERT(nsegs == 1, ("%s: too many DMA segments",
			device_get_nameunit(sc->sc_dev)));

		desc->sdp0 = htole32(segs[0].ds_addr);
		desc->sdl0 = htole16(MJUMPAGESIZE);
	}

	error = bus_dmamap_create(ring->data_dma_tag, 0,
	    &ring->spare_dma_map);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not create Rx spare DMA map\n");
		goto fail;
	}

	bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
		BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

	return 0;

fail:
	rt2860_free_rx_ring(sc, ring);

	return error;
}

/*
 * rt2860_reset_rx_ring
 */
static void
rt2860_reset_rx_ring(struct rt2860_softc *sc,
    struct rt2860_softc_rx_ring *ring)
{
	struct rt2860_rxd *desc;
	int i;

	for (i = 0; i < RT2860_SOFTC_RX_RING_DATA_COUNT; i++) {
		desc = &ring->desc[i];

		desc->sdl0 &= ~htole16(RT2860_RX_DDONE);
	}

	bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
		BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

	ring->cur = 0;
}

/*
 * rt2860_free_rx_ring
 */
static void
rt2860_free_rx_ring(struct rt2860_softc *sc,
    struct rt2860_softc_rx_ring *ring)
{
	struct rt2860_softc_rx_data *data;
	int i;

	if (ring->desc != NULL) {
		bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
			BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(ring->desc_dma_tag, ring->desc_dma_map);
		bus_dmamem_free(ring->desc_dma_tag, ring->desc,
			ring->desc_dma_map);
	}

	if (ring->desc_dma_tag != NULL)
		bus_dma_tag_destroy(ring->desc_dma_tag);

	for (i = 0; i < RT2860_SOFTC_RX_RING_DATA_COUNT; i++) {
		data = &ring->data[i];

		if (data->m != NULL) {
			bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
				BUS_DMASYNC_POSTREAD);
			bus_dmamap_unload(ring->data_dma_tag, data->dma_map);
			m_freem(data->m);
		}

		if (data->dma_map != NULL)
			bus_dmamap_destroy(ring->data_dma_tag, data->dma_map);
	}

	if (ring->spare_dma_map != NULL)
		bus_dmamap_destroy(ring->data_dma_tag, ring->spare_dma_map);

	if (ring->data_dma_tag != NULL)
		bus_dma_tag_destroy(ring->data_dma_tag);
}

/*
 * rt2860_alloc_tx_ring
 */
static int
rt2860_alloc_tx_ring(struct rt2860_softc *sc,
    struct rt2860_softc_tx_ring *ring, int qid)
{
	struct rt2860_softc_tx_data *data;
	int error, i, size;

	size = RT2860_SOFTC_TX_RING_DESC_COUNT * sizeof(struct rt2860_txd);
	mtx_init(&ring->lock, device_get_nameunit(sc->sc_dev), NULL, MTX_DEF);

	error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), PAGE_SIZE, 0,
	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
	    size, 1, size, 0, NULL, NULL, &ring->desc_dma_tag);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not create Tx desc DMA tag\n");
		goto fail;
	}

	error = bus_dmamem_alloc(ring->desc_dma_tag, (void **) &ring->desc,
	    BUS_DMA_NOWAIT | BUS_DMA_ZERO, &ring->desc_dma_map);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not allocate Tx desc DMA memory\n");
		goto fail;
	}

	error = bus_dmamap_load(ring->desc_dma_tag, ring->desc_dma_map,
	    ring->desc, size, rt2860_dma_map_addr, &ring->desc_phys_addr, 0);
	if (error != 0) {
		device_printf(sc->sc_dev, "could not load Tx desc DMA map\n");
		goto fail;
	}

	ring->desc_queued = 0;
	ring->desc_cur = 0;
	ring->desc_next = 0;

	size = RT2860_SOFTC_TX_RING_DATA_COUNT * RT2860_TX_DATA_SEG0_SIZE;
	error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), PAGE_SIZE, 0,
	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
	    size, 1, size, 0, NULL, NULL, &ring->seg0_dma_tag);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not create Tx seg0 DMA tag\n");
		goto fail;
	}

	error = bus_dmamem_alloc(ring->seg0_dma_tag, (void **) &ring->seg0,
	    BUS_DMA_NOWAIT | BUS_DMA_ZERO, &ring->seg0_dma_map);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not allocate Tx seg0 DMA memory\n");
		goto fail;
	}

	error = bus_dmamap_load(ring->seg0_dma_tag, ring->seg0_dma_map,
	    ring->seg0, size, rt2860_dma_map_addr, &ring->seg0_phys_addr, 0);
	if (error != 0) {
		device_printf(sc->sc_dev, "could not load Tx seg0 DMA map\n");
		goto fail;
	}


	error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), PAGE_SIZE, 0,
	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
	    MJUMPAGESIZE, RT2860_SOFTC_MAX_SCATTER, MJUMPAGESIZE, 0,
	    NULL, NULL, &ring->data_dma_tag);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "could not create Tx data DMA tag\n");
		goto fail;
	}

	for (i = 0; i < RT2860_SOFTC_TX_RING_DATA_COUNT; i++){
		data = &ring->data[i];

		error = bus_dmamap_create(ring->data_dma_tag, 0,
		    &data->dma_map);
		if (error != 0) {
			device_printf(sc->sc_dev,
			    "could not create Tx data DMA map\n");
			goto fail;
		}
	}

	ring->data_queued = 0;
	ring->data_cur = 0;
	ring->data_next = 0;

	ring->qid = qid;

	return 0;

fail:
	rt2860_free_tx_ring(sc, ring);

	return error;
}

/*
 * rt2860_reset_tx_ring
 */
static void
rt2860_reset_tx_ring(struct rt2860_softc *sc,
    struct rt2860_softc_tx_ring *ring)
{
	struct rt2860_softc_tx_data *data;
	struct rt2860_txd *desc;
	int i;

	for (i = 0; i < RT2860_SOFTC_TX_RING_DESC_COUNT; i++) {
		desc = &ring->desc[i];

		desc->sdl0 = 0;
		desc->sdl1 = 0;
	}

	ring->desc_queued = 0;
	ring->desc_cur = 0;
	ring->desc_next = 0;

	bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
		BUS_DMASYNC_PREWRITE);

	bus_dmamap_sync(ring->seg0_dma_tag, ring->seg0_dma_map,
		BUS_DMASYNC_PREWRITE);

	for (i = 0; i < RT2860_SOFTC_TX_RING_DATA_COUNT; i++) {
		data = &ring->data[i];

		if (data->m != NULL) {
			bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
				BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(ring->data_dma_tag, data->dma_map);
			m_freem(data->m);
			data->m = NULL;
		}

		if (data->ni != NULL) {
			ieee80211_free_node(data->ni);
			data->ni = NULL;
		}
	}

	ring->data_queued = 0;
	ring->data_cur = 0;
	ring->data_next = 0;
}

/*
 * rt2860_free_tx_ring
 */
static void
rt2860_free_tx_ring(struct rt2860_softc *sc,
    struct rt2860_softc_tx_ring *ring)
{
	struct rt2860_softc_tx_data *data;
	int i;

	if (ring->desc != NULL) {
		bus_dmamap_sync(ring->desc_dma_tag, ring->desc_dma_map,
			BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(ring->desc_dma_tag, ring->desc_dma_map);
		bus_dmamem_free(ring->desc_dma_tag, ring->desc,
			ring->desc_dma_map);
	}

	if (ring->desc_dma_tag != NULL)
		bus_dma_tag_destroy(ring->desc_dma_tag);

	if (ring->seg0 != NULL) {
		bus_dmamap_sync(ring->seg0_dma_tag, ring->seg0_dma_map,
			BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(ring->seg0_dma_tag, ring->seg0_dma_map);
		bus_dmamem_free(ring->seg0_dma_tag, ring->seg0,
			ring->seg0_dma_map);
	}

	if (ring->seg0_dma_tag != NULL)
		bus_dma_tag_destroy(ring->seg0_dma_tag);

	for (i = 0; i < RT2860_SOFTC_TX_RING_DATA_COUNT; i++) {
		data = &ring->data[i];

		if (data->m != NULL) {
			bus_dmamap_sync(ring->data_dma_tag, data->dma_map,
				BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(ring->data_dma_tag, data->dma_map);
			m_freem(data->m);
		}

		if (data->ni != NULL)
			ieee80211_free_node(data->ni);

		if (data->dma_map != NULL)
			bus_dmamap_destroy(ring->data_dma_tag, data->dma_map);
	}

	if (ring->data_dma_tag != NULL)
		bus_dma_tag_destroy(ring->data_dma_tag);

	mtx_destroy(&ring->lock);
}

/*
 * rt2860_dma_map_addr
 */
static void
rt2860_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
	if (error != 0)
		return;

	KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg));

	*(bus_addr_t *) arg = segs[0].ds_addr;
}

/*
 * rt2860_sysctl_attach
 */
static void
rt2860_sysctl_attach(struct rt2860_softc *sc)
{
	struct sysctl_ctx_list *ctx;
	struct sysctl_oid *tree;
	struct sysctl_oid *stats;

	ctx = device_get_sysctl_ctx(sc->sc_dev);
	tree = device_get_sysctl_tree(sc->sc_dev);

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		"tx_stbc", CTLFLAG_RW, &sc->tx_stbc, 0,
		"Tx STBC");

	/* statistic counters */

	stats = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		"stats", CTLFLAG_RD, 0, "statistic");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"interrupts", CTLFLAG_RD,
		&sc->interrupts, 0,
		"all interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_coherent_interrupts", CTLFLAG_RD,
		&sc->tx_coherent_interrupts, 0,
		"Tx coherent interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_coherent_interrupts", CTLFLAG_RD,
		&sc->rx_coherent_interrupts, 0,
		"Rx coherent interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"txrx_coherent_interrupts", CTLFLAG_RD,
		&sc->txrx_coherent_interrupts, 0,
		"Tx/Rx coherent interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"fifo_sta_full_interrupts", CTLFLAG_RD,
		&sc->fifo_sta_full_interrupts, 0,
		"FIFO statistic full interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_interrupts", CTLFLAG_RD,
		&sc->rx_interrupts, 0,
		"Rx interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_mgmt_interrupts", CTLFLAG_RD,
		&sc->tx_interrupts[5], 0,
		"Tx MGMT interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_hcca_interrupts", CTLFLAG_RD,
		&sc->tx_interrupts[4], 0,
		"Tx HCCA interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac3_interrupts", CTLFLAG_RD,
		&sc->tx_interrupts[3], 0,
		"Tx AC3 interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac2_interrupts", CTLFLAG_RD,
		&sc->tx_interrupts[2], 0,
		"Tx AC2 interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac1_interrupts", CTLFLAG_RD,
		&sc->tx_interrupts[1], 0,
		"Tx AC1 interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac0_interrupts", CTLFLAG_RD,
		&sc->tx_interrupts[0], 0,
		"Tx AC0 interrupts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_mgmt_desc_queued", CTLFLAG_RD,
		&sc->tx_ring[5].desc_queued, 0,
		"Tx MGMT descriptors queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_mgmt_data_queued", CTLFLAG_RD,
		&sc->tx_ring[5].data_queued, 0,
		"Tx MGMT data queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_hcca_desc_queued", CTLFLAG_RD,
		&sc->tx_ring[4].desc_queued, 0,
		"Tx HCCA descriptors queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_hcca_data_queued", CTLFLAG_RD,
		&sc->tx_ring[4].data_queued, 0,
		"Tx HCCA data queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac3_desc_queued", CTLFLAG_RD,
		&sc->tx_ring[3].desc_queued, 0,
		"Tx AC3 descriptors queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac3_data_queued", CTLFLAG_RD,
		&sc->tx_ring[3].data_queued, 0,
		"Tx AC3 data queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac2_desc_queued", CTLFLAG_RD,
		&sc->tx_ring[2].desc_queued, 0,
		"Tx AC2 descriptors queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac2_data_queued", CTLFLAG_RD,
		&sc->tx_ring[2].data_queued, 0,
		"Tx AC2 data queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac1_desc_queued", CTLFLAG_RD,
		&sc->tx_ring[1].desc_queued, 0,
		"Tx AC1 descriptors queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac1_data_queued", CTLFLAG_RD,
		&sc->tx_ring[1].data_queued, 0,
		"Tx AC1 data queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac0_desc_queued", CTLFLAG_RD,
		&sc->tx_ring[0].desc_queued, 0,
		"Tx AC0 descriptors queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac0_data_queued", CTLFLAG_RD,
		&sc->tx_ring[0].data_queued, 0,
		"Tx AC0 data queued");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_mgmt_data_queue_full", CTLFLAG_RD,
		&sc->tx_data_queue_full[5], 0,
		"Tx MGMT data queue full");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_hcca_data_queue_full", CTLFLAG_RD,
		&sc->tx_data_queue_full[4], 0,
		"Tx HCCA data queue full");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac3_data_queue_full", CTLFLAG_RD,
		&sc->tx_data_queue_full[3], 0,
		"Tx AC3 data queue full");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac2_data_queue_full", CTLFLAG_RD,
		&sc->tx_data_queue_full[2], 0,
		"Tx AC2 data queue full");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac1_data_queue_full", CTLFLAG_RD,
		&sc->tx_data_queue_full[1], 0,
		"Tx AC1 data queue full");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ac0_data_queue_full", CTLFLAG_RD,
		&sc->tx_data_queue_full[0], 0,
		"Tx AC0 data queue full");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_watchdog_timeouts", CTLFLAG_RD,
		&sc->tx_watchdog_timeouts, 0,
		"Tx watchdog timeouts");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_defrag_packets", CTLFLAG_RD,
		&sc->tx_defrag_packets, 0,
		"Tx defragmented packets");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"no_tx_desc_avail", CTLFLAG_RD,
		&sc->no_tx_desc_avail, 0,
		"no Tx descriptors available");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_mbuf_alloc_errors", CTLFLAG_RD,
		&sc->rx_mbuf_alloc_errors, 0,
		"Rx mbuf allocation errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_mbuf_dmamap_errors", CTLFLAG_RD,
		&sc->rx_mbuf_dmamap_errors, 0,
		"Rx mbuf DMA mapping errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_queue_0_not_empty", CTLFLAG_RD,
		&sc->tx_queue_not_empty[0], 0,
		"Tx queue 0 not empty");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_queue_1_not_empty", CTLFLAG_RD,
		&sc->tx_queue_not_empty[1], 0,
		"Tx queue 1 not empty");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_beacons", CTLFLAG_RD,
		&sc->tx_beacons, 0,
		"Tx beacons");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_noretryok", CTLFLAG_RD,
		&sc->tx_noretryok, 0,
		"Tx successfull without retries");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_retryok", CTLFLAG_RD,
		&sc->tx_retryok, 0,
		"Tx successfull with retries");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_failed", CTLFLAG_RD,
		&sc->tx_failed, 0,
		"Tx failed");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_underflows", CTLFLAG_RD,
		&sc->tx_underflows, 0,
		"Tx underflows");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_zerolen", CTLFLAG_RD,
		&sc->tx_zerolen, 0,
		"Tx zero length");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_nonagg", CTLFLAG_RD,
		&sc->tx_nonagg, 0,
		"Tx non-aggregated");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_agg", CTLFLAG_RD,
		&sc->tx_agg, 0,
		"Tx aggregated");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ampdu", CTLFLAG_RD,
		&sc->tx_ampdu, 0,
		"Tx A-MPDU");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_mpdu_zero_density", CTLFLAG_RD,
		&sc->tx_mpdu_zero_density, 0,
		"Tx MPDU with zero density");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"tx_ampdu_sessions", CTLFLAG_RD,
		&sc->tx_ampdu_sessions, 0,
		"Tx A-MPDU sessions");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_packets", CTLFLAG_RD,
		&sc->rx_packets, 0,
		"Rx packets");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_ampdu", CTLFLAG_RD,
		&sc->rx_ampdu, 0,
		"Rx A-MPDU");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_ampdu_retries", CTLFLAG_RD,
		&sc->rx_ampdu_retries, 0,
		"Rx A-MPDU retries");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_mpdu_zero_density", CTLFLAG_RD,
		&sc->rx_mpdu_zero_density, 0,
		"Rx MPDU with zero density");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_ampdu_sessions", CTLFLAG_RD,
		&sc->rx_ampdu_sessions, 0,
		"Rx A-MPDU sessions");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_amsdu", CTLFLAG_RD,
		&sc->rx_amsdu, 0,
		"Rx A-MSDU");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_crc_errors", CTLFLAG_RD,
		&sc->rx_crc_errors, 0,
		"Rx CRC errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_phy_errors", CTLFLAG_RD,
		&sc->rx_phy_errors, 0,
		"Rx PHY errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_false_ccas", CTLFLAG_RD,
		&sc->rx_false_ccas, 0,
		"Rx false CCAs");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_plcp_errors", CTLFLAG_RD,
		&sc->rx_plcp_errors, 0,
		"Rx PLCP errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_dup_packets", CTLFLAG_RD,
		&sc->rx_dup_packets, 0,
		"Rx duplicate packets");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_fifo_overflows", CTLFLAG_RD,
		&sc->rx_fifo_overflows, 0,
		"Rx FIFO overflows");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_cipher_no_errors", CTLFLAG_RD,
		&sc->rx_cipher_no_errors, 0,
		"Rx cipher no errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_cipher_icv_errors", CTLFLAG_RD,
		&sc->rx_cipher_icv_errors, 0,
		"Rx cipher ICV errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_cipher_mic_errors", CTLFLAG_RD,
		&sc->rx_cipher_mic_errors, 0,
		"Rx cipher MIC errors");

	SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(stats), OID_AUTO,
		"rx_cipher_invalid_key_errors", CTLFLAG_RD,
		&sc->rx_cipher_invalid_key_errors, 0,
		"Rx cipher invalid key errors");
}

/*
 * Defines and macros
 */

#define RT2860_AMRR_IS_SUCCESS(amrr_node)				\
    ((amrr_node)->retrycnt < (amrr_node)->txcnt / 10)

#define RT2860_AMRR_IS_FAILURE(amrr_node)				\
    ((amrr_node)->retrycnt > (amrr_node)->txcnt / 3)

#define RT2860_AMRR_IS_ENOUGH(amrr_node)				\
    ((amrr_node)->txcnt > 10)

/*
 * Static function prototypes
 */

static int rt2860_amrr_update(struct rt2860_amrr *amrr,
	struct rt2860_amrr_node *amrr_node, struct ieee80211_node *ni);

/*
 * rt2860_amrr_init
 */
void rt2860_amrr_init(struct rt2860_amrr *amrr, struct ieee80211vap *vap,
    int ntxpath, int min_success_threshold, int max_success_threshold,
    int msecs)
{
	int t;

	amrr->ntxpath = ntxpath;

	amrr->min_success_threshold = min_success_threshold;
	amrr->max_success_threshold = max_success_threshold;

	if (msecs < 100)
		msecs = 100;

	t = msecs_to_ticks(msecs);

	amrr->interval = (t < 1) ? 1 : t;
}

/*
 * rt2860_amrr_cleanup
 */
void rt2860_amrr_cleanup(struct rt2860_amrr *amrr)
{
}

/*
 * rt2860_amrr_node_init
 */
void rt2860_amrr_node_init(struct rt2860_amrr *amrr,
    struct rt2860_amrr_node *amrr_node, struct ieee80211_node *ni)
{
	const struct ieee80211_rateset *rs;

	amrr_node->amrr = amrr;
	amrr_node->success = 0;
	amrr_node->recovery = 0;
	amrr_node->txcnt = 0;
	amrr_node->retrycnt = 0;
	amrr_node->success_threshold = amrr->min_success_threshold;

	if (ni->ni_flags & IEEE80211_NODE_HT) {
		rs = (const struct ieee80211_rateset *) &ni->ni_htrates;

		for (amrr_node->rate_index = rs->rs_nrates - 1;
			amrr_node->rate_index > 0 &&
			    (rs->rs_rates[amrr_node->rate_index] &
				IEEE80211_RATE_VAL) > 4;
			amrr_node->rate_index--) ;

		ni->ni_txrate = rs->rs_rates[amrr_node->rate_index] |
		    IEEE80211_RATE_MCS;
	} else {
		rs = &ni->ni_rates;

		for (amrr_node->rate_index = rs->rs_nrates - 1;
			amrr_node->rate_index > 0 &&
			    (rs->rs_rates[amrr_node->rate_index] &
				IEEE80211_RATE_VAL) > 72;
			amrr_node->rate_index--) ;

		ni->ni_txrate = rs->rs_rates[amrr_node->rate_index] &
		    IEEE80211_RATE_VAL;
	}

	amrr_node->ticks = ticks;
}

/*
 * rt2860_amrr_choose
 */
int rt2860_amrr_choose(struct ieee80211_node *ni,
	struct rt2860_amrr_node *amrr_node)
{
	struct rt2860_amrr *amrr;
	int rate_index;

	amrr = amrr_node->amrr;

	if (RT2860_AMRR_IS_ENOUGH(amrr_node) &&
		(ticks - amrr_node->ticks) > amrr->interval) {
		rate_index = rt2860_amrr_update(amrr, amrr_node, ni);
		if (rate_index != amrr_node->rate_index) {
			if (ni->ni_flags & IEEE80211_NODE_HT)
				ni->ni_txrate =
				    ni->ni_htrates.rs_rates[rate_index] |
				    IEEE80211_RATE_MCS;
			else
				ni->ni_txrate =
				    ni->ni_rates.rs_rates[rate_index] &
				    IEEE80211_RATE_VAL;

			amrr_node->rate_index = rate_index;
		}

		amrr_node->ticks = ticks;
	} else {
		rate_index = amrr_node->rate_index;
	}

	return rate_index;
}

/*
 * rt2860_amrr_update
 */
static int
rt2860_amrr_update(struct rt2860_amrr *amrr,
	struct rt2860_amrr_node *amrr_node, struct ieee80211_node *ni)
{
	const struct ieee80211_rateset *rs;
	int rate_index;

	KASSERT(RT2860_AMRR_IS_ENOUGH(amrr_node),
		("not enough Tx count: txcnt=%d", amrr_node->txcnt));

	if (ni->ni_flags & IEEE80211_NODE_HT)
		rs = (const struct ieee80211_rateset *) &ni->ni_htrates;
	else
		rs = &ni->ni_rates;

	rate_index = amrr_node->rate_index;

	if (RT2860_AMRR_IS_SUCCESS(amrr_node)) {
		amrr_node->success++;
		if ((amrr_node->success >= amrr_node->success_threshold) &&
		    (rate_index + 1 < rs->rs_nrates) &&
		    (!(ni->ni_flags & IEEE80211_NODE_HT) ||
			(rs->rs_rates[rate_index + 1] & IEEE80211_RATE_VAL) <
		    (amrr->ntxpath * 8))) {
			amrr_node->recovery = 1;
			amrr_node->success = 0;

			rate_index++;
		} else {
			amrr_node->recovery = 0;
		}
	} else if (RT2860_AMRR_IS_FAILURE(amrr_node)) {
		amrr_node->success = 0;

		if (rate_index > 0) {
			if (amrr_node->recovery) {
				amrr_node->success_threshold *= 2;
				if (amrr_node->success_threshold >
				    amrr->max_success_threshold)
					amrr_node->success_threshold =
					    amrr->max_success_threshold;
			} else {
				amrr_node->success_threshold =
				    amrr->min_success_threshold;
			}

			rate_index--;
		}

		amrr_node->recovery = 0;
	}

	amrr_node->txcnt = 0;
	amrr_node->retrycnt = 0;

	return rate_index;
}

/*
 * Defines and macros
 */

/*
 * RT2860_IO_EEPROM_RAISE_CLK
 */
#define RT2860_IO_EEPROM_RAISE_CLK(sc, val)				\
do									\
{									\
	(val) |= RT2860_REG_EESK;					\
									\
	RAL_WRITE((sc), RT2860_PCI_EECTRL, (val));	\
									\
	DELAY(1);							\
} while (0)

/*
 * RT2860_IO_EEPROM_LOWER_CLK
 */
#define RT2860_IO_EEPROM_LOWER_CLK(sc, val)				\
do									\
{									\
	(val) &= ~RT2860_REG_EESK;					\
									\
	RAL_WRITE((sc), RT2860_PCI_EECTRL, (val));	\
									\
	DELAY(1);							\
} while (0)

#define RT2860_IO_BYTE_CRC16(byte, crc)					\
	((uint16_t) (((crc) << 8) ^					\
	    rt2860_io_ccitt16[(((crc) >> 8) ^ (byte)) & 255]))

/* #ifdef RT305X_SOC */
static const uint16_t rt3052_eeprom[] =
{
	0x3052, 0x0101, 0x0c00, 0x3043, 0x8852, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0x0c00, 0x3043, 0x7752, 0x0c00,
	0x3043, 0x6652, 0x0822, 0x0024, 0xffff, 0x012f, 0x7755, 0xaaa8,
	0x888c, 0xffff, 0x000c, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff,
	0xffff, 0x0d0d, 0x0d0d, 0x0c0c, 0x0c0c, 0x0c0c, 0x0c0c, 0x0c0c,
	0x1010, 0x1111, 0x1211, 0x1212, 0x1313, 0x1413, 0x1414, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6666,
	0xaacc, 0x6688, 0xaacc, 0x6688, 0xaacc, 0x6688, 0xaacc, 0x6688,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
};

uint8_t rt3052_rf_default[] = {
	0x50, /* 0 */
	0x01,
	0xF7,
	0x75,
	0x40,
	0x03,
	0x42,
	0x50,
	0x39,
	0x0F,
	0x60, /* 10 */
	0x21,
	0x75,
	0x75,
	0x90,
	0x58,
	0xB3,
	0x92,
	0x2C,
	0x02,
	0xBA, /* 20 */
	0xDB,
	0x00,
	0x31,
	0x08,
	0x01,
	0x25, /* Core Power: 0x25=1.25V */
	0x23, /* RF: 1.35V */
      	0x13, /* ADC: must consist with R27 */
	0x83,
	0x00, /* 30 */
	0x00,
};

/* #endif */


/*
 * Static variables
 */

static const uint16_t rt2860_io_ccitt16[] =
{
	0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
	0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
	0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
	0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
	0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
	0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
	0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
	0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
	0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
	0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
	0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
	0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
	0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
	0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
	0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
	0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
	0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
	0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
	0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
	0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
	0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
	0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
	0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
	0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
	0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
	0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
	0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
	0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
	0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
	0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
	0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
	0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

/* Read 16-bit from eFUSE ROM (>=RT3071 only.) */
static uint16_t
rt3090_efuse_read_2(struct rt2860_softc *sc, uint16_t addr)
{
	uint32_t tmp;
	uint16_t reg;
	int ntries;

	addr *= 2;
	/*-
	 * Read one 16-byte block into registers EFUSE_DATA[0-3]:
	 * DATA0: F E D C
	 * DATA1: B A 9 8
	 * DATA2: 7 6 5 4
	 * DATA3: 3 2 1 0
	 */
	tmp = RAL_READ(sc, RT3070_EFUSE_CTRL);
	tmp &= ~(RT3070_EFSROM_MODE_MASK | RT3070_EFSROM_AIN_MASK);
	tmp |= (addr & ~0xf) << RT3070_EFSROM_AIN_SHIFT | RT3070_EFSROM_KICK;
	RAL_WRITE(sc, RT3070_EFUSE_CTRL, tmp);

	for (ntries = 0; ntries < 500; ntries++) {
		tmp = RAL_READ(sc, RT3070_EFUSE_CTRL);
		if (!(tmp & RT3070_EFSROM_KICK))
			break;
		DELAY(2);
	}
	if (ntries == 500)
		return 0xffff;

	if ((tmp & RT3070_EFUSE_AOUT_MASK) == RT3070_EFUSE_AOUT_MASK)
		return 0xffff;	/* address not found */

	/* determine to which 32-bit register our 16-bit word belongs */
	reg = RT3070_EFUSE_DATA3 - (addr & 0xc);
	tmp = RAL_READ(sc, reg);

	return (addr & 2) ? tmp >> 16 : tmp & 0xffff;
}


/*
 * rt2860_io_eeprom_read
 * addr is 16bit word address
 */
uint16_t rt2860_io_eeprom_read(struct rt2860_softc *sc, uint16_t addr)
{
	uint32_t tmp;
	uint16_t byteaddr, val;
	int n;

	byteaddr = (addr << 1);

	if (sc->mac_rev == 0x28720200) {
		/* SoC embedded version, don't have EEPROM */
		/* index of 16bit words array */
		return (rt3052_eeprom[addr]);
	} else if ((sc->mac_rev & 0xffff0000) >= 0x30710000) {
		tmp = RAL_READ(sc, RT3070_EFUSE_CTRL);
		if (tmp & RT3070_SEL_EFUSE)
			return (rt3090_efuse_read_2(sc, addr));
	}

	/* clock C once before the first command */
	RT2860_EEPROM_CTL(sc, 0);

	RT2860_EEPROM_CTL(sc, RT2860_S);
	RT2860_EEPROM_CTL(sc, RT2860_S | RT2860_C);
	RT2860_EEPROM_CTL(sc, RT2860_S);

	/* write start bit (1) */
	RT2860_EEPROM_CTL(sc, RT2860_S | RT2860_D);
	RT2860_EEPROM_CTL(sc, RT2860_S | RT2860_D | RT2860_C);

	/* write READ opcode (10) */
	RT2860_EEPROM_CTL(sc, RT2860_S | RT2860_D);
	RT2860_EEPROM_CTL(sc, RT2860_S | RT2860_D | RT2860_C);
	RT2860_EEPROM_CTL(sc, RT2860_S);
	RT2860_EEPROM_CTL(sc, RT2860_S | RT2860_C);

	/* write address (A5-A0 or A7-A0) */
	n = ((RAL_READ(sc, RT2860_PCI_EECTRL) & 0x30) == 0) ? 5 : 7;
	for (; n >= 0; n--) {
		RT2860_EEPROM_CTL(sc, RT2860_S |
		    (((byteaddr >> n) & 1) << RT2860_SHIFT_D));
		RT2860_EEPROM_CTL(sc, RT2860_S |
		    (((byteaddr >> n) & 1) << RT2860_SHIFT_D) | RT2860_C);
	}

	RT2860_EEPROM_CTL(sc, RT2860_S);

	/* read data Q15-Q0 */
	val = 0;
	for (n = 15; n >= 0; n--) {
		RT2860_EEPROM_CTL(sc, RT2860_S | RT2860_C);
		tmp = RAL_READ(sc, RT2860_PCI_EECTRL);
		val |= ((tmp & RT2860_Q) >> RT2860_SHIFT_Q) << n;
		RT2860_EEPROM_CTL(sc, RT2860_S);
	}
	RT2860_EEPROM_CTL(sc, 0);

	/* clear Chip Select and clock C */
	RT2860_EEPROM_CTL(sc, RT2860_S);
	RT2860_EEPROM_CTL(sc, 0);
	RT2860_EEPROM_CTL(sc, RT2860_C);

	return val;
}

/*
 * rt2860_io_bbp_read
 */
uint8_t rt2860_io_bbp_read(struct rt2860_softc *sc, uint8_t reg)
{
	int ntries;
	uint32_t tmp;

	if (sc->mac_rev == 0x28720200) {
		for (ntries = 0; ntries < 100; ntries ++) {
			if ( !(RAL_READ(sc, RT2860_REG_BBP_CSR_CFG) &
			    RT2860_REG_BBP_CSR_BUSY) )
				break;
			DELAY(1);
		}
		if (ntries == 100) {
			device_printf(sc->sc_dev,
			    "BBP busy after 100 probes\n");
			return (0);
		}
		RAL_WRITE(sc, RT2860_REG_BBP_CSR_CFG,
		    RT2860_REG_BBP_CSR_READ |
		    RT2860_REG_BBP_CSR_KICK |
		    RT2860_REG_BBP_RW_MODE_PARALLEL |
		    (reg & RT2860_REG_BBP_REG_MASK) <<
			RT2860_REG_BBP_REG_SHIFT);
		for (ntries = 0; ntries < 100; ntries ++) {
			if ( !(RAL_READ(sc,
			    RT2860_REG_BBP_CSR_CFG) &
			    RT2860_REG_BBP_CSR_BUSY) )
				break;
			DELAY(1);
		}
		if (ntries == 100) {
			device_printf(sc->sc_dev,
			    "BBP busy after 100 probes\n");
			return (0);
		} else {
			return ((RAL_READ(sc,
			    RT2860_REG_BBP_CSR_CFG) >>
			    RT2860_REG_BBP_VAL_SHIFT) &
			    RT2860_REG_BBP_VAL_MASK);
		}
		return (0);
	}

	for (ntries = 0; ntries < 100; ntries++) {
		if (!(RAL_READ(sc,
		    RT2860_REG_H2M_MAILBOX_BBP_AGENT) &
			RT2860_REG_BBP_CSR_BUSY))
			break;

		DELAY(1);
	}

	if (ntries == 100) {
		device_printf(sc->sc_dev,
		    "could not read from BBP through MCU: reg=0x%02x\n", reg);
		return 0;
	}

	tmp = RT2860_REG_BBP_RW_MODE_PARALLEL |
		RT2860_REG_BBP_CSR_BUSY |
		RT2860_REG_BBP_CSR_READ |
		((reg & RT2860_REG_BBP_REG_MASK) << RT2860_REG_BBP_REG_SHIFT);

	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX_BBP_AGENT, tmp);

	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_BBP,
		RT2860_TOKEN_NO_INTR, 0);

	DELAY(1000);

	for (ntries = 0; ntries < 100; ntries++) {
		tmp = RAL_READ(sc, RT2860_REG_H2M_MAILBOX_BBP_AGENT);
		if (!(tmp & RT2860_REG_BBP_CSR_BUSY))
			return ((tmp >> RT2860_REG_BBP_VAL_SHIFT) &
				RT2860_REG_BBP_VAL_MASK);

		DELAY(1);
	}

	device_printf(sc->sc_dev, "could not read from BBP through MCU: "
	    "reg=0x%02x\n", reg);

	return 0;
}

/*
 * rt2860_io_bbp_write
 */
void rt2860_io_bbp_write(struct rt2860_softc *sc, uint8_t reg, uint8_t val)
{
	int ntries;
	uint32_t tmp;

	if (sc->mac_rev == 0x28720200) {
		for (ntries = 0; ntries < 100; ntries ++) {
			if ( !(RAL_READ(sc, RT2860_REG_BBP_CSR_CFG) &
			    RT2860_REG_BBP_CSR_BUSY) )
				break;
			DELAY(1);
		}
		if (ntries == 100) {
			device_printf(sc->sc_dev,
			    "BBP busy after 100 probes\n");
			return;
		}
		RAL_WRITE(sc, RT2860_REG_BBP_CSR_CFG,
		    RT2860_REG_BBP_CSR_KICK | RT2860_REG_BBP_RW_MODE_PARALLEL |
		    (reg & RT2860_REG_BBP_REG_MASK) << RT2860_REG_BBP_REG_SHIFT |
		    (val & RT2860_REG_BBP_VAL_MASK) << RT2860_REG_BBP_VAL_SHIFT );
		rt2860_io_bbp_read(sc, reg);
		return;
	}

	for (ntries = 0; ntries < 100; ntries++) {
		if (!(RAL_READ(sc, RT2860_REG_H2M_MAILBOX_BBP_AGENT) &
			RT2860_REG_BBP_CSR_BUSY))
			break;

		DELAY(1);
	}

	if (ntries == 100) {
		device_printf(sc->sc_dev,
		    "could not write to BBP through MCU: reg=0x%02x\n", reg);
		return;
	}

	tmp = RT2860_REG_BBP_RW_MODE_PARALLEL |
		RT2860_REG_BBP_CSR_BUSY |
		((reg & RT2860_REG_BBP_REG_MASK) << RT2860_REG_BBP_REG_SHIFT) |
		((val & RT2860_REG_BBP_VAL_MASK) << RT2860_REG_BBP_VAL_SHIFT);

	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX_BBP_AGENT, tmp);

	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_BBP,
		RT2860_TOKEN_NO_INTR, 0);

	DELAY(1000);
}

/*
 * rt2860_io_rf_write
 */
void rt2860_io_rf_write(struct rt2860_softc *sc, uint8_t reg, uint32_t val)
{
	int ntries;
	if (sc->mac_rev == 0x28720200) {
		for (ntries = 0; ntries < 100; ntries ++) {
			if ( !(RAL_READ(sc, RT2872_REG_RF_CSR_CFG) &
			    RT2872_REG_RF_CSR_BUSY) )
				break;
			DELAY(1);
		}
		if (ntries == 100) {
			device_printf(sc->sc_dev,
			    "RF busy after 100 probes\n");
			return;
		}
		RAL_WRITE(sc, RT2872_REG_RF_CSR_CFG,
		    RT2872_REG_RF_CSR_KICK | RT2872_REG_RF_CSR_WRITE |
		    (reg & RT2872_REG_RF_ID_MASK) << RT2872_REG_RF_ID_SHIFT |
		    (val & RT2872_REG_RF_VAL_MASK) << RT2872_REG_RF_VAL_SHIFT );
		rt2860_io_rf_read(sc, reg);
		return;
	}


	for (ntries = 0; ntries < 100; ntries++)
		if (!(RAL_READ(sc, RT2860_REG_RF_CSR_CFG0) &
			RT2860_REG_RF_BUSY))
			break;

	if (ntries == 100) {
		device_printf(sc->sc_dev,
		    "could not write to RF: reg=0x%02x\n", reg);
		return;
	}

	RAL_WRITE(sc, RT2860_REG_RF_CSR_CFG0, val);
}

/*
 * rt2860_io_rf_read
 */
int32_t rt2860_io_rf_read(struct rt2860_softc *sc, uint8_t reg)
{
	int ntries;
	if (sc->mac_rev == 0x28720200) {
		for (ntries = 0; ntries < 100; ntries ++) {
			if ( !(RAL_READ(sc, RT2872_REG_RF_CSR_CFG) &
			    RT2872_REG_RF_CSR_BUSY) )
				break;
			DELAY(1);
		}
		if (ntries == 100) {
			device_printf(sc->sc_dev,
			    "RF busy after 100 probes\n");
			return (-1);
		}
		RAL_WRITE(sc, RT2872_REG_RF_CSR_CFG,
		    RT2872_REG_RF_CSR_KICK |
		    (reg & RT2872_REG_RF_ID_MASK) << RT2872_REG_RF_ID_SHIFT );

		for (ntries = 0; ntries < 100; ntries ++) {
			if ( !(RAL_READ(sc, RT2872_REG_RF_CSR_CFG) &
			    RT2872_REG_RF_CSR_BUSY) )
				break;
			DELAY(1);
		}
		if (ntries == 100) {
			device_printf(sc->sc_dev,
			    "RF busy after 100 probes\n");
		}

		return (RAL_READ(sc, RT2872_REG_RF_CSR_CFG) & RT2872_REG_RF_VAL_MASK);
	}
	return (-1);
}

/*
 * rt2860_io_rf_load_defaults
 */
void rt2860_io_rf_load_defaults(struct rt2860_softc *sc)
{
	int i;

	if (sc->mac_rev == 0x28720200) {
		for (i = 0; i < 32; i ++)
			rt2860_io_rf_write(sc, i, rt3052_rf_default[i]);
	}
}

/*
 * rt2860_io_mcu_cmd
 */
void rt2860_io_mcu_cmd(struct rt2860_softc *sc, uint8_t cmd,
	uint8_t token, uint16_t arg)
{
	uint32_t tmp;
	int ntries;

	if (sc->mac_rev == 0x28720200)
		return;

	for (ntries = 0; ntries < 100; ntries++) {
		if (!(RAL_READ(sc, RT2860_REG_H2M_MAILBOX) &
			RT2860_H2M_BUSY))
			break;

		DELAY(2);
	}

	if (ntries == 100) {
		device_printf(sc->sc_dev,
		    "could not read H2M: cmd=0x%02x\n", cmd);
		return;
	}

	tmp = RT2860_H2M_BUSY | (token << 16) | arg;

	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX, tmp);
	RAL_WRITE(sc, RT2860_REG_H2M_HOST_CMD, cmd);
}

/*
 * rt2860_io_mcu_cmd_check
 */
int rt2860_io_mcu_cmd_check(struct rt2860_softc *sc, uint8_t cid)
{
	uint32_t tmp, mask, status;
	int result, ntries;

	result = -1;

	for (ntries = 0; ntries < 200; ntries++) {
		tmp = RAL_READ(sc, RT2860_REG_H2M_MAILBOX_CID);

		if (((cid >> RT2860_REG_H2M_CID0_SHIFT) &
		    RT2860_REG_H2M_CID_MASK) == cid) {
			mask = (RT2860_REG_H2M_CID_MASK <<
			    RT2860_REG_H2M_CID0_SHIFT);
			break;
		} else if (((tmp >> RT2860_REG_H2M_CID1_SHIFT) &
		    RT2860_REG_H2M_CID_MASK) == cid) {
			mask = (RT2860_REG_H2M_CID_MASK <<
			    RT2860_REG_H2M_CID1_SHIFT);
			break;
		} else if (((tmp >> RT2860_REG_H2M_CID2_SHIFT) &
		    RT2860_REG_H2M_CID_MASK) == cid) {
			mask = (RT2860_REG_H2M_CID_MASK <<
			    RT2860_REG_H2M_CID2_SHIFT);
			break;
		} else if (((tmp >> RT2860_REG_H2M_CID3_SHIFT) &
		    RT2860_REG_H2M_CID_MASK) == cid) {
			mask = (RT2860_REG_H2M_CID_MASK <<
			    RT2860_REG_H2M_CID3_SHIFT);
			break;
		}

		DELAY(100);
	}

	status = RAL_READ(sc, RT2860_REG_H2M_MAILBOX_STATUS);

	if (ntries < 200) {
		status &= mask;

		if ((status == 0x1) ||
			(status == 0x100) ||
			(status == 0x10000) ||
			(status == 0x1000000))
			result = 0;
	}

	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX_STATUS, 0xffffffff);
	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX_CID, 0xffffffff);

	return result;
}

int
rt2860_load_microcode(struct rt2860_softc *sc)
{
	struct ifnet *ifp = sc->sc_ifp;
	const struct firmware *fp;
	int ntries, error = 0;

	RT2860_SOFTC_UNLOCK(sc);
	fp = firmware_get("rt2860fw");
	RT2860_SOFTC_LOCK(sc);
	if (fp == NULL) {
		if_printf(ifp,
		    "%s: unable to retrieve firmware image rt2860fw\n",
		    __func__);
		return EINVAL;
	}

	/* set "host program ram write selection" bit */
	RAL_WRITE(sc, RT2860_REG_PBF_SYS_CTRL, RT2860_REG_HST_PM_SEL);
	/* write microcode image */
	RAL_WRITE_REGION_1(sc, RT2860_REG_MCU_UCODE_BASE, fp->data,
	    fp->datasize);
	/* kick microcontroller unit */
	RAL_WRITE(sc, RT2860_REG_PBF_SYS_CTRL, 0);
	RAL_BARRIER_WRITE(sc);
	RAL_WRITE(sc, RT2860_REG_PBF_SYS_CTRL, RT2860_REG_MCU_RESET);

	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX_BBP_AGENT, 0);
	RAL_WRITE(sc, RT2860_REG_H2M_MAILBOX, 0);

	/* wait until microcontroller is ready */
	RAL_BARRIER_READ_WRITE(sc);
	for (ntries = 0; ntries < 1000; ntries++) {
		if (RAL_READ(sc, RT2860_REG_PBF_SYS_CTRL) &
			RT2860_REG_MCU_READY)
			break;
		DELAY(1000);
	}
	if (ntries == 1000) {
		device_printf(sc->sc_dev,
		    "timeout waiting for MCU to initialize\n");
		error = ETIMEDOUT;
	}
	firmware_put(fp, FIRMWARE_UNLOAD);
	return error;
}

/*
 * rt2860_led_brightness
 */
void rt2860_led_brightness(struct rt2860_softc *sc, uint8_t brightness)
{
	uint8_t polarity;
	uint16_t tmp;

	polarity = (sc->led_cntl & RT2860_EEPROM_LED_POLARITY) ? 1 : 0;

	tmp = (polarity << 8) | brightness;

	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_LED_BRIGHTNESS,
		RT2860_TOKEN_NO_INTR, tmp);
}

/*
 * rt2860_led_cmd
 */
void rt2860_led_cmd(struct rt2860_softc *sc, uint8_t cmd)
{
	uint16_t tmp;

	tmp = (cmd << 8) | (sc->led_cntl & RT2860_EEPROM_LED_MODE_MASK);

	rt2860_io_mcu_cmd(sc, RT2860_IO_MCU_CMD_LEDS,
		RT2860_TOKEN_NO_INTR, tmp);
}

/*
 * rt2860_read_eeprom
 */
void rt2860_read_eeprom(struct rt2860_softc *sc)
{
	uint32_t tmp;
	uint16_t val;
	int i;

	sc->debug |= RT2860_DEBUG_EEPROM; /* XXX: debug only */

	/* read EEPROM address number */
	tmp = RAL_READ(sc, RT2860_PCI_EECTRL);
	if((tmp & 0x30) == 0)
		sc->eeprom_addr_num = 6;
	else if((tmp & 0x30) == 0x10)
		sc->eeprom_addr_num = 8;
	else
		sc->eeprom_addr_num = 8;

	/* read EEPROM version */
	sc->eeprom_rev = rt2860_io_eeprom_read(sc, RT2860_EEPROM_VERSION);

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM rev=0x%04x\n",
		 sc->eeprom_rev);

	/* read MAC address */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_MAC01);
	sc->mac_addr[0] = (val & 0xff);
	sc->mac_addr[1] = (val >> 8);
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_MAC23);
	sc->mac_addr[2] = (val & 0xff);
	sc->mac_addr[3] = (val >> 8);
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_MAC45);
	sc->mac_addr[4] = (val & 0xff);
	sc->mac_addr[5] = (val >> 8);
	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM mac address=%s\n",
		 ether_sprintf(sc->mac_addr));

	/* read RF information */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_ANTENNA);
	if (val == 0xffff) {
		device_printf(sc->sc_dev, "invalid EEPROM antenna info\n");

		sc->rf_rev = RT2860_RF_2820;
		sc->ntxpath = 1;
		sc->nrxpath = 2;
	} else {
		sc->rf_rev = (val >> 8) & 0xf;
		sc->ntxpath = (val >> 4) & 0xf;
		sc->nrxpath = (val & 0xf);
	}

	if ((sc->mac_rev != 0x28830300) && (sc->nrxpath > 2)) {
		/* only 2 Rx streams for RT2860 series */
		sc->nrxpath = 2;
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
	    "EEPROM RF rev=0x%04x, paths=%dT%dR\n",
	     sc->rf_rev, sc->ntxpath,
	    sc->nrxpath);

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_CONFIG);
	if ((val & 0xff00) != 0xff00)
		sc->patch_dac = (val >> 15) & 1;

	sc->hw_radio_cntl = ((val & RT2860_EEPROM_HW_RADIO_CNTL) ? 1 : 0);
	sc->tx_agc_cntl = ((val & RT2860_EEPROM_TX_AGC_CNTL) ? 1 : 0);
	sc->ext_lna_2ghz = ((val & RT2860_EEPROM_EXT_LNA_2GHZ) ? 1 : 0);
	sc->ext_lna_5ghz = ((val & RT2860_EEPROM_EXT_LNA_5GHZ) ? 1 : 0);

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
	    "EEPROM NIC config: HW radio cntl=%d, Tx AGC cntl=%d, "
	    "ext LNA gains=%d/%d\n",
	    sc->hw_radio_cntl, sc->tx_agc_cntl, sc->ext_lna_2ghz,
	    sc->ext_lna_5ghz);

	/* read country code */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_COUNTRY);
	sc->country_2ghz = (val >> 8) & 0xff;
	sc->country_5ghz = (val & 0xff);
	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
	    "EEPROM country code=%d/%d\n",
	     sc->country_2ghz,
	    sc->country_5ghz);

	/* read RF frequency offset */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_FREQ_LEDS);
	if ((val & 0xff) != 0xff) {
		sc->rf_freq_off = (val & 0xff);
	} else {
		device_printf(sc->sc_dev, "invalid EEPROM RF freq offset\n");
		sc->rf_freq_off = 0;
	}
	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM freq offset=0x%02x\n",
		 sc->rf_freq_off);

	/* read LEDs operating mode */
	if (((val >> 8) & 0xff) != 0xff) {
		sc->led_cntl = ((val >> 8) & 0xff);
		sc->led_off[0] = rt2860_io_eeprom_read(sc, RT2860_EEPROM_LED1);
		sc->led_off[1] = rt2860_io_eeprom_read(sc, RT2860_EEPROM_LED2);
		sc->led_off[2] = rt2860_io_eeprom_read(sc, RT2860_EEPROM_LED3);
	} else {
		device_printf(sc->sc_dev, "invalid EEPROM LED settings\n");

		sc->led_cntl = RT2860_EEPROM_LED_CNTL_DEFAULT;
		sc->led_off[0] = RT2860_EEPROM_LED1_OFF_DEFAULT;
		sc->led_off[1] = RT2860_EEPROM_LED2_OFF_DEFAULT;
		sc->led_off[2] = RT2860_EEPROM_LED3_OFF_DEFAULT;
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
	    "EEPROM LED cntl=0x%02x, LEDs=0x%04x/0x%04x/0x%04x\n",
	     sc->led_cntl,
	    sc->led_off[0], sc->led_off[1], sc->led_off[2]);

	/* read RSSI offsets and LNA gains */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_LNA);
	if ((sc->mac_rev & 0xffff0000) >= 0x30710000)
		sc->lna_gain[0] = RT3090_DEF_LNA;
	else				/* channel group 0 */
		sc->lna_gain[0] = val & 0xff;

	sc->lna_gain[1] = (val >> 8) & 0xff;

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_RSSI1_2GHZ);
	sc->rssi_off_2ghz[0] = (val & 0xff);
	sc->rssi_off_2ghz[1] = (val >> 8) & 0xff;

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_RSSI2_2GHZ);

	if ((sc->mac_rev & 0xffff0000) >= 0x30710000) {
		/*
		 * On RT3090 chips (limited to 2 Rx chains), this ROM
		 * field contains the Tx mixer gain for the 2GHz band.
		 */
		if ((val & 0xff) != 0xff)
			sc->txmixgain_2ghz = val & 0x7;
	} else
		sc->rssi_off_2ghz[2] = val & 0xff;	/* Ant C */
	sc->lna_gain[2] = (val >> 8) & 0xff;

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_RSSI1_5GHZ);
	sc->rssi_off_5ghz[0] = (val & 0xff);
	sc->rssi_off_5ghz[1] = (val >> 8) & 0xff;

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_RSSI2_5GHZ);
	sc->rssi_off_5ghz[2] = (val & 0xff);
	sc->lna_gain[3] = (val >> 8) & 0xff;

	for (i = 2; i < RT2860_SOFTC_LNA_GAIN_COUNT; i++) {
		if (sc->lna_gain[i] == 0x00 ||
		    sc->lna_gain[i] == (int8_t) 0xff) {
			device_printf(sc->sc_dev,
			    "invalid EEPROM LNA gain #%d: 0x%02x\n",
			    i, sc->lna_gain[i]);

			sc->lna_gain[i] = sc->lna_gain[1];
		}
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM LNA gains=0x%02x/0x%02x/0x%02x/0x%02x\n",
		sc->lna_gain[0], sc->lna_gain[1], sc->lna_gain[2], sc->lna_gain[3]);

	for (i = 0; i < RT2860_SOFTC_RSSI_OFF_COUNT; i++) {
		if (sc->rssi_off_2ghz[i] < RT2860_EEPROM_RSSI_OFF_MIN ||
			sc->rssi_off_2ghz[i] > RT2860_EEPROM_RSSI_OFF_MAX) {
			device_printf(sc->sc_dev,
			    "invalid EEPROM RSSI offset #%d (2GHz): 0x%02x\n",
			    i, sc->rssi_off_2ghz[i]);

			sc->rssi_off_2ghz[i] = 0;
		}

		if (sc->rssi_off_5ghz[i] < RT2860_EEPROM_RSSI_OFF_MIN ||
			sc->rssi_off_5ghz[i] > RT2860_EEPROM_RSSI_OFF_MAX) {
			device_printf(sc->sc_dev,
			    "invalid EEPROM RSSI offset #%d (5GHz): 0x%02x\n",
			    i, sc->rssi_off_5ghz[i]);

			sc->rssi_off_5ghz[i] = 0;
		}
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM RSSI offsets 2GHz=%d/%d/%d\n",
		sc->rssi_off_2ghz[0], sc->rssi_off_2ghz[1],
		sc->rssi_off_2ghz[2]);

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM RSSI offsets 5GHz=%d/%d/%d\n",
		sc->rssi_off_5ghz[0], sc->rssi_off_5ghz[1],
		sc->rssi_off_5ghz[2]);

	/* read Tx power settings for 2GHz channels */
	for (i = 0; i < 14; i += 2) {
		val = rt2860_io_eeprom_read(sc,
		    RT2860_EEPROM_PWR2GHZ_BASE1 + i / 4);

		sc->txpow1[i + 0] = (int8_t) (val & 0xff);
		sc->txpow1[i + 1] = (int8_t) (val >> 8);

		val = rt2860_io_eeprom_read(sc,
		    RT2860_EEPROM_PWR2GHZ_BASE2 + i / 4);

		sc->txpow2[i + 0] = (int8_t) (val & 0xff);
		sc->txpow2[i + 1] = (int8_t) (val >> 8);
		RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		    "Tx power (2GHz) %d:%d/%d:%d\n",
		    sc->txpow1[i + 1],sc->txpow1[i],
		    sc->txpow2[i + 1],sc->txpow2[i]);
	}

	/* read Tx power settings for 5GHz channels */
	for (; i < RT2860_SOFTC_TXPOW_COUNT; i += 2)
	{
		val = rt2860_io_eeprom_read(sc,
		    RT2860_EEPROM_PWR5GHZ_BASE1 + i / 4);

		sc->txpow1[i + 0] = (int8_t) (val & 0xff);
		sc->txpow1[i + 1] = (int8_t) (val >> 8);

		val = rt2860_io_eeprom_read(sc,
		    RT2860_EEPROM_PWR5GHZ_BASE2 + i / 4);

		sc->txpow2[i + 0] = (int8_t) (val & 0xff);
		sc->txpow2[i + 1] = (int8_t) (val >> 8);
	}

	/* fix broken Tx power settings */
	for (i = 0; i < 14; i++) {
		if (sc->txpow1[i] < RT2860_EEPROM_TXPOW_2GHZ_MIN ||
			sc->txpow1[i] > RT2860_EEPROM_TXPOW_2GHZ_MAX) {
			device_printf(sc->sc_dev,
			    "invalid EEPROM Tx power1 #%d (2GHz): 0x%02x\n",
			    i, sc->txpow1[i]);

			sc->txpow1[i] = RT2860_EEPROM_TXPOW_2GHZ_DEFAULT;
		}

		if (sc->txpow2[i] < RT2860_EEPROM_TXPOW_2GHZ_MIN ||
			sc->txpow2[i] > RT2860_EEPROM_TXPOW_2GHZ_MAX) {
			device_printf(sc->sc_dev,
			    "invalid EEPROM Tx power2 #%d (2GHz): 0x%02x\n",
			    i, sc->txpow2[i]);

			sc->txpow2[i] = RT2860_EEPROM_TXPOW_2GHZ_DEFAULT;
		}
	}

	for (; i < RT2860_SOFTC_TXPOW_COUNT; i++) {
		if (sc->txpow1[i] < RT2860_EEPROM_TXPOW_5GHZ_MIN ||
			sc->txpow1[i] > RT2860_EEPROM_TXPOW_5GHZ_MAX) {
			device_printf(sc->sc_dev,
			    "invalid EEPROM Tx power1 #%d (5GHz): 0x%02x\n",
			    i, sc->txpow1[i]);

			sc->txpow1[i] = RT2860_EEPROM_TXPOW_5GHZ_DEFAULT;
		}

		if (sc->txpow2[i] < RT2860_EEPROM_TXPOW_5GHZ_MIN ||
			sc->txpow2[i] > RT2860_EEPROM_TXPOW_5GHZ_MAX) {
			device_printf(sc->sc_dev,
			    "invalid EEPROM Tx power2 #%d (5GHz): 0x%02x\n",
			    i, sc->txpow2[i]);

			sc->txpow2[i] = RT2860_EEPROM_TXPOW_5GHZ_DEFAULT;
		}
	}

	/* read Tx power per rate deltas */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_DELTAPWR);

	sc->txpow_rate_delta_2ghz = 0;
	sc->txpow_rate_delta_5ghz = 0;

	if ((val & 0xff) != 0xff) {
		if (val & 0x80)
			sc->txpow_rate_delta_2ghz = (val & 0xf);

		if (!(val & 0x40))
			sc->txpow_rate_delta_2ghz =
			    -sc->txpow_rate_delta_2ghz;
	}

	val >>= 8;

	if ((val & 0xff) != 0xff) {
		if (val & 0x80)
			sc->txpow_rate_delta_5ghz = (val & 0xf);

		if (!(val & 0x40))
			sc->txpow_rate_delta_5ghz =
			    -sc->txpow_rate_delta_5ghz;
	}

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM Tx power per rate deltas=%d(2MHz), %d(5MHz)\n",
		sc->txpow_rate_delta_2ghz, sc->txpow_rate_delta_5ghz);

	/* read Tx power per rate */
	for (i = 0; i < 5; i++) {
		tmp = rt2860_io_eeprom_read(sc, RT2860_EEPROM_RPWR + i * 2);
		tmp |= (rt2860_io_eeprom_read(sc, RT2860_EEPROM_RPWR + i * 2 + 1) << 16);

		sc->txpow_rate_20mhz[i] = tmp;
		sc->txpow_rate_40mhz_2ghz[i] =
			rt2860_read_eeprom_txpow_rate_add_delta(tmp, sc->txpow_rate_delta_2ghz);
		sc->txpow_rate_40mhz_5ghz[i] =
			rt2860_read_eeprom_txpow_rate_add_delta(tmp, sc->txpow_rate_delta_5ghz);

		RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
			"EEPROM Tx power per rate #%d=0x%08x(20MHz), 0x%08x(40MHz/2GHz), 0x%08x(40MHz/5GHz)\n",
			i, sc->txpow_rate_20mhz[i], sc->txpow_rate_40mhz_2ghz[i], sc->txpow_rate_40mhz_5ghz[i]);
	}

	if (sc->tx_agc_cntl)
		sc->tx_agc_cntl_2ghz = sc->tx_agc_cntl_5ghz = 1;

	/* read factory-calibrated samples for temperature compensation */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI1_2GHZ);
	sc->tssi_2ghz[0] = (val & 0xff);	/* [-4] */
	sc->tssi_2ghz[1] = (val >> 8);		/* [-3] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI2_2GHZ);
	sc->tssi_2ghz[2] = (val & 0xff);	/* [-2] */
	sc->tssi_2ghz[3] = (val >> 8);		/* [-1] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI3_2GHZ);
	sc->tssi_2ghz[4] = (val & 0xff);	/* [0] */
	sc->tssi_2ghz[5] = (val >> 8);		/* [+1] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI4_2GHZ);
	sc->tssi_2ghz[6] = (val & 0xff);	/* [+2] */
	sc->tssi_2ghz[7] = (val >> 8);		/* [+3] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI5_2GHZ);
	sc->tssi_2ghz[8] = (val & 0xff);	/* [+4] */
	sc->tssi_step_2ghz = (val >> 8);

	if (sc->tssi_2ghz[4] == 0xff)
		sc->tx_agc_cntl_2ghz = 0;

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
	    "EEPROM TSSI 2GHz: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, "
	    "0x%02x, 0x%02x, step=%d\n",
	    sc->tssi_2ghz[0], sc->tssi_2ghz[1], sc->tssi_2ghz[2],
	    sc->tssi_2ghz[3], sc->tssi_2ghz[4], sc->tssi_2ghz[5],
	    sc->tssi_2ghz[6], sc->tssi_2ghz[7], sc->tssi_2ghz[8],
	    sc->tssi_step_2ghz);

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI1_5GHZ);
	sc->tssi_5ghz[0] = (val & 0xff);	/* [-4] */
	sc->tssi_5ghz[1] = (val >> 8);		/* [-3] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI2_5GHZ);
	sc->tssi_5ghz[2] = (val & 0xff);	/* [-2] */
	sc->tssi_5ghz[3] = (val >> 8);		/* [-1] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI3_5GHZ);
	sc->tssi_5ghz[4] = (val & 0xff);	/* [0] */
	sc->tssi_5ghz[5] = (val >> 8);		/* [+1] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI4_5GHZ);
	sc->tssi_5ghz[6] = (val & 0xff);	/* [+2] */
	sc->tssi_5ghz[7] = (val >> 8);		/* [+3] */

	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_TSSI5_5GHZ);
	sc->tssi_5ghz[8] = (val & 0xff);	/* [+4] */
	sc->tssi_step_5ghz = (val >> 8);

	if (sc->tssi_5ghz[4] == 0xff)
		sc->tx_agc_cntl_5ghz = 0;

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
	    "EEPROM TSSI 5GHz: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, "
	    "0x%02x, 0x%02x, step=%d\n",
	    sc->tssi_5ghz[0], sc->tssi_5ghz[1], sc->tssi_5ghz[2],
	    sc->tssi_5ghz[3], sc->tssi_5ghz[4], sc->tssi_5ghz[5],
	    sc->tssi_5ghz[6], sc->tssi_5ghz[7], sc->tssi_5ghz[8],
	    sc->tssi_step_5ghz);

	/* read default BBP settings */
	for (i = 0; i < 8; i++) {
		val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_BBP_BASE + i);
		sc->bbp_eeprom[i].val = val & 0xff;
		sc->bbp_eeprom[i].reg = val >> 8;
	}
	if ((sc->mac_rev & 0xffff0000) >= 0x30710000) {
		/* read vendor RF settings */
		for (i = 0; i < 10; i++) {
			val = rt2860_io_eeprom_read(sc, RT3071_EEPROM_RF_BASE + i);
			sc->rf[i].val = val & 0xff;
			sc->rf[i].reg = val >> 8;
		}
	}

	/* read powersave level */
	val = rt2860_io_eeprom_read(sc, RT2860_EEPROM_PCIE_PSLEVEL);

	sc->powersave_level = val & 0xff;

	if ((sc->powersave_level & 0xff) == 0xff)
		device_printf(sc->sc_dev, "invalid EEPROM powersave level\n");

	RT2860_DPRINTF(sc, RT2860_DEBUG_EEPROM,
		"EEPROM powersave level=0x%02x\n",
		 sc->powersave_level);
}

/*
 * rt2860_read_eeprom_txpow_rate_add_delta
 */
uint32_t rt2860_read_eeprom_txpow_rate_add_delta(uint32_t txpow_rate,
	int8_t delta)
{
	int8_t b4;
	int i;

	for (i = 0; i < 8; i++) {
		b4 = txpow_rate & 0xf;
		b4 += delta;

		if (b4 < 0)
			b4 = 0;
		else if (b4 > 0xf)
			b4 = 0xf;

		txpow_rate = (txpow_rate >> 4) | (b4 << 28);
	}

	return txpow_rate;
}


uint8_t
rt3090_rf_read(struct rt2860_softc *sc, uint8_t reg)
{
	uint32_t tmp;
	int ntries;

	for (ntries = 0; ntries < 100; ntries++) {
		if (!(RAL_READ(sc, RT3070_RF_CSR_CFG) & RT3070_RF_KICK))
			break;
		DELAY(1);
	}
	if (ntries == 100) {
		device_printf(sc->sc_dev, "could not read RF register\n");
		return 0xff;
	}
	tmp = RT3070_RF_KICK | reg << 8;
	RAL_WRITE(sc, RT3070_RF_CSR_CFG, tmp);

	for (ntries = 0; ntries < 100; ntries++) {
		tmp = RAL_READ(sc, RT3070_RF_CSR_CFG);
		if (!(tmp & RT3070_RF_KICK))
			break;
		DELAY(1);
	}
	if (ntries == 100) {
		device_printf(sc->sc_dev, "could not read RF register\n");
		return 0xff;
	}
	return tmp & 0xff;
}

void
rt3090_rf_write(struct rt2860_softc *sc, uint8_t reg, uint8_t val)
{
	uint32_t tmp;
	int ntries;

	for (ntries = 0; ntries < 10; ntries++) {
		if (!(RAL_READ(sc, RT3070_RF_CSR_CFG) & RT3070_RF_KICK))
			break;
		DELAY(10);
	}
	if (ntries == 10) {
		device_printf(sc->sc_dev, "could not write to RF\n");
		return;
	}

	tmp = RT3070_RF_WRITE | RT3070_RF_KICK | reg << 8 | val;
	RAL_WRITE(sc, RT3070_RF_CSR_CFG, tmp);
}

/*
 * rt2860_rf_name
 */
const char *rt2860_rf_name(int rf_rev)
{
	switch (rf_rev) {
	case RT2860_RF_2820:
		return "RT2820 2.4G 2T3R";

	case RT2860_RF_2850:
		return "RT2850 2.4G/5G 2T3R";

	case RT2860_RF_2720:
		return "RT2720 2.4G 1T2R";

	case RT2860_RF_2750:
		return "RT2750 2.4G/5G 1T2R";

	case RT3070_RF_3020:
		return "RT3020 2.4G 1T1R";

	case RT3070_RF_2020:
		return "RT2020 2.4G B/G";

	case RT3070_RF_3021:
		return "RT3021 2.4G 1T2R";

	case RT3070_RF_3022:
		return "RT3022 2.4G 2T2R";

	case RT3070_RF_3052:
		return "RT3052 2.4G/5G 2T2R";

	case RT2860_RF_2853:
		return "RT2853 2.4G.5G 3T3R";

	case RT3070_RF_3320:
		return "RT3320 2.4G 1T1R with PA";

	case RT3070_RF_3322:
		return "RT3322 2.4G 2T2R with PA";

	case RT3070_RF_3053:
		return "RT3053 2.4G/5G 3T3R";

	default:
		return "unknown";
	}
}

/*
 * rt2860_rf_select_chan_group
 */
void rt2860_rf_select_chan_group(struct rt2860_softc *sc,
	struct ieee80211_channel *c)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	int chan, group;
	uint32_t tmp;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	chan = ieee80211_chan2ieee(ic, c);
	if (chan == 0 || chan == IEEE80211_CHAN_ANY)
		return;

	if (chan <= 14)
		group = 0;
	else if (chan <= 64)
		group = 1;
	else if (chan <= 128)
		group = 2;
	else
		group = 3;

	rt2860_io_bbp_write(sc, 62, 0x37 - sc->lna_gain[group]);
	rt2860_io_bbp_write(sc, 63, 0x37 - sc->lna_gain[group]);
	rt2860_io_bbp_write(sc, 64, 0x37 - sc->lna_gain[group]);
	rt2860_io_bbp_write(sc, 86, 0x00);

	if (group == 0) {
		if (sc->ext_lna_2ghz) {
			rt2860_io_bbp_write(sc, 82, 0x62);
			rt2860_io_bbp_write(sc, 75, 0x46);
		} else {
			rt2860_io_bbp_write(sc, 82, 0x84);
			rt2860_io_bbp_write(sc, 75, 0x50);
		}
	} else {
		rt2860_io_bbp_write(sc, 82, 0xf2);

		if (sc->ext_lna_5ghz)
			rt2860_io_bbp_write(sc, 75, 0x46);
		else
			rt2860_io_bbp_write(sc, 75, 0x50);
	}

	if (group == 0) {
		tmp = 0x2e + sc->lna_gain[group];
	} else {
		if ((ic->ic_flags & IEEE80211_F_SCAN) ||
		    !IEEE80211_IS_CHAN_HT40(c))
			tmp = 0x32 + sc->lna_gain[group] * 5 / 3;
		else
			tmp = 0x3a + sc->lna_gain[group] * 5 / 3;
	}

	rt2860_io_bbp_write(sc, 66, tmp);

	tmp = RT2860_REG_RFTR_ENABLE |
		RT2860_REG_TRSW_ENABLE |
		RT2860_REG_LNA_PE_G1_ENABLE |
		RT2860_REG_LNA_PE_A1_ENABLE |
		RT2860_REG_LNA_PE_G0_ENABLE |
		RT2860_REG_LNA_PE_A0_ENABLE;

	if (group == 0)
		tmp |= RT2860_REG_PA_PE_G1_ENABLE |
			RT2860_REG_PA_PE_G0_ENABLE;
	else
		tmp |= RT2860_REG_PA_PE_A1_ENABLE |
			RT2860_REG_PA_PE_A0_ENABLE;

	if (sc->ntxpath == 1)
		tmp &= ~(RT2860_REG_PA_PE_G1_ENABLE | RT2860_REG_PA_PE_A1_ENABLE);

	if (sc->nrxpath == 1)
		tmp &= ~(RT2860_REG_LNA_PE_G1_ENABLE | RT2860_REG_LNA_PE_A1_ENABLE);

	RAL_WRITE(sc, RT2860_REG_TX_PIN_CFG, tmp);

	tmp = RAL_READ(sc, RT2860_REG_TX_BAND_CFG);

	tmp &= ~(RT2860_REG_TX_BAND_BG | RT2860_REG_TX_BAND_A | RT2860_REG_TX_BAND_HT40_ABOVE);

	if (group == 0)
		tmp |= RT2860_REG_TX_BAND_BG;
	else
		tmp |= RT2860_REG_TX_BAND_A;

	/* set central channel position */

	if (IEEE80211_IS_CHAN_HT40U(c))
		tmp |= RT2860_REG_TX_BAND_HT40_BELOW;
	else if (IEEE80211_IS_CHAN_HT40D(c))
		tmp |= RT2860_REG_TX_BAND_HT40_ABOVE;
	else
		tmp |= RT2860_REG_TX_BAND_HT40_BELOW;

	RAL_WRITE(sc, RT2860_REG_TX_BAND_CFG, tmp);

	/* set bandwidth (20MHz or 40MHz) */

	tmp = rt2860_io_bbp_read(sc, 4);

	tmp &= ~0x18;

	if (IEEE80211_IS_CHAN_HT40(c))
		tmp |= 0x10;

	rt2860_io_bbp_write(sc, 4, tmp);

	/* set central channel position */

	tmp = rt2860_io_bbp_read(sc, 3);

	tmp &= ~0x20;

	if (IEEE80211_IS_CHAN_HT40D(c))
		tmp |= 0x20;

	rt2860_io_bbp_write(sc, 3, tmp);

	if (sc->mac_rev == 0x28600100) {
		if (!IEEE80211_IS_CHAN_HT40(c)) {
			rt2860_io_bbp_write(sc, 69, 0x16);
			rt2860_io_bbp_write(sc, 70, 0x08);
			rt2860_io_bbp_write(sc, 73, 0x12);
		} else {
			rt2860_io_bbp_write(sc, 69, 0x1a);
			rt2860_io_bbp_write(sc, 70, 0x0a);
			rt2860_io_bbp_write(sc, 73, 0x16);
		}
	}

}

void
rt3090_set_chan(struct rt2860_softc *sc, u_int chan)
{
	int8_t txpow1, txpow2;
	uint8_t rf;
	int i;

	KASSERT((chan >= 1 && chan <= 14), "RT3090 is 2GHz only");	/* RT3090 is 2GHz only */

	/* find the settings for this channel (we know it exists) */
	for (i = 0; rt2860_rf2850[i].chan != chan; i++);

	/* use Tx power values from EEPROM */
	txpow1 = sc->txpow1[i];
	txpow2 = sc->txpow2[i];

	rt3090_rf_write(sc, 2, rt3090_freqs[i].n);
	rf = rt3090_rf_read(sc, 3);
	rf = (rf & ~0x0f) | rt3090_freqs[i].k;
	rt3090_rf_write(sc, 3, rf);
	rf = rt3090_rf_read(sc, 6);
	rf = (rf & ~0x03) | rt3090_freqs[i].r;
	rt3090_rf_write(sc, 6, rf);

	/* set Tx0 power */
	rf = rt3090_rf_read(sc, 12);
	rf = (rf & ~0x1f) | txpow1;
	rt3090_rf_write(sc, 12, rf);

	/* set Tx1 power */
	rf = rt3090_rf_read(sc, 13);
	rf = (rf & ~0x1f) | txpow2;
	rt3090_rf_write(sc, 13, rf);

	rf = rt3090_rf_read(sc, 1);
	rf &= ~0xfc;
	if (sc->ntxpath == 1)
		rf |= RT3070_TX1_PD | RT3070_TX2_PD;
	else if (sc->ntxpath == 2)
		rf |= RT3070_TX2_PD;
	if (sc->nrxpath == 1)
		rf |= RT3070_RX1_PD | RT3070_RX2_PD;
	else if (sc->nrxpath == 2)
		rf |= RT3070_RX2_PD;
	rt3090_rf_write(sc, 1, rf);

	/* set RF offset */
	rf = rt3090_rf_read(sc, 23);
	rf = (rf & ~0x7f) | sc->rf_freq_off;
	rt3090_rf_write(sc, 23, rf);

	/* program RF filter */
	rf = rt3090_rf_read(sc, 24);	/* Tx */
	rf = (rf & ~0x3f) | sc->rf24_20mhz;
	rt3090_rf_write(sc, 24, rf);
	rf = rt3090_rf_read(sc, 31);	/* Rx */
	rf = (rf & ~0x3f) | sc->rf24_20mhz;
	rt3090_rf_write(sc, 31, rf);

	/* enable RF tuning */
	rf = rt3090_rf_read(sc, 7);
	rt3090_rf_write(sc, 7, rf | RT3070_TUNE);
}

int
rt3090_rf_init(struct rt2860_softc *sc)
{
	uint32_t tmp;
	uint8_t rf, bbp;
	int i;

	rf = rt3090_rf_read(sc, 30);
	/* toggle RF R30 bit 7 */
	rt3090_rf_write(sc, 30, rf | 0x80);
	DELAY(1000);
	rt3090_rf_write(sc, 30, rf & ~0x80);

	tmp = RAL_READ(sc, RT3070_LDO_CFG0);
	tmp &= ~0x1f000000;
	if (sc->patch_dac && (sc->mac_rev & 0x0000ffff) < 0x0211)
		tmp |= 0x0d000000;	/* 1.35V */
	else
		tmp |= 0x01000000;	/* 1.2V */
	RAL_WRITE(sc, RT3070_LDO_CFG0, tmp);

	/* patch LNA_PE_G1 */
	tmp = RAL_READ(sc, RT3070_GPIO_SWITCH);
	RAL_WRITE(sc, RT3070_GPIO_SWITCH, tmp & ~0x20);

	/* initialize RF registers to default value */
	for (i = 0; i < (sizeof(rt3090_def_rf)/2); i++) {
		rt3090_rf_write(sc, rt3090_def_rf[i].reg,
		    rt3090_def_rf[i].val);
	}

	/* select 20MHz bandwidth */
	rt3090_rf_write(sc, 31, 0x14);

	rf = rt3090_rf_read(sc, 6);
	rt3090_rf_write(sc, 6, rf | 0x40);

	if ((sc->mac_rev & 0xffff0000) != 0x35930000) {
		/* calibrate filter for 20MHz bandwidth */
		sc->rf24_20mhz = 0x1f;	/* default value */
		rt3090_filter_calib(sc, 0x07, 0x16, &sc->rf24_20mhz);

		/* select 40MHz bandwidth */
		bbp = rt2860_io_bbp_read(sc, 4);
		rt2860_io_bbp_write(sc, 4, (bbp & ~0x08) | 0x10);
		rf = rt3090_rf_read(sc, 31);
		rt3090_rf_write(sc, 31, rf | 0x20);

		/* calibrate filter for 40MHz bandwidth */
		sc->rf24_40mhz = 0x2f;	/* default value */
		rt3090_filter_calib(sc, 0x27, 0x19, &sc->rf24_40mhz);

		/* go back to 20MHz bandwidth */
		bbp = rt2860_io_bbp_read(sc, 4);
		rt2860_io_bbp_write(sc, 4, bbp & ~0x18);
	}
	if ((sc->mac_rev & 0x0000ffff) < 0x0211)
		rt3090_rf_write(sc, 27, 0x03);

	tmp = RAL_READ(sc, RT3070_OPT_14);
	RAL_WRITE(sc, RT3070_OPT_14, tmp | 1);

	if (sc->rf_rev == RT3070_RF_3020)
		rt3090_set_rx_antenna(sc, 0);

	bbp = rt2860_io_bbp_read(sc, 138);
	if ((sc->mac_rev & 0xffff0000) == 0x35930000) {
		if (sc->ntxpath == 1)
			bbp |= 0x60;	/* turn off DAC1 and DAC2 */
		else if (sc->ntxpath == 2)
			bbp |= 0x40;	/* turn off DAC2 */
		if (sc->nrxpath == 1)
			bbp &= ~0x06;	/* turn off ADC1 and ADC2 */
		else if (sc->nrxpath == 2)
			bbp &= ~0x04;	/* turn off ADC2 */
	} else {
		if (sc->ntxpath == 1)
			bbp |= 0x20;	/* turn off DAC1 */
		if (sc->nrxpath == 1)
			bbp &= ~0x02;	/* turn off ADC1 */
	}
	rt2860_io_bbp_write(sc, 138, bbp);

	rf = rt3090_rf_read(sc, 1);
	rf &= ~(RT3070_RX0_PD | RT3070_TX0_PD);
	rf |= RT3070_RF_BLOCK | RT3070_RX1_PD | RT3070_TX1_PD;
	rt3090_rf_write(sc, 1, rf);

	rf = rt3090_rf_read(sc, 15);
	rt3090_rf_write(sc, 15, rf & ~RT3070_TX_LO2);

	rf = rt3090_rf_read(sc, 17);
	rf &= ~RT3070_TX_LO1;
	if ((sc->mac_rev & 0x0000ffff) >= 0x0211 && !sc->ext_lna_2ghz)
		rf |= 0x20;	/* fix for long range Rx issue */
	if (sc->txmixgain_2ghz >= 2)
		rf = (rf & ~0x7) | sc->txmixgain_2ghz;
	rt3090_rf_write(sc, 17, rf);

	rf = rt3090_rf_read(sc, 20);
	rt3090_rf_write(sc, 20, rf & ~RT3070_RX_LO1);

	rf = rt3090_rf_read(sc, 21);
	rt3090_rf_write(sc, 21, rf & ~RT3070_RX_LO2);

	return 0;
}

void
rt3090_set_rx_antenna(struct rt2860_softc *sc, int aux)
{
	uint32_t tmp;

	if (aux) {
		tmp = RAL_READ(sc, RT2860_PCI_EECTRL);
		RAL_WRITE(sc, RT2860_PCI_EECTRL, tmp & ~RT2860_C);
		tmp = RAL_READ(sc, RT2860_REG_SCHDMA_GPIO_CTRL_CFG);
		RAL_WRITE(sc, RT2860_REG_SCHDMA_GPIO_CTRL_CFG, (tmp & ~0x0808) | 0x08);
	} else {
		tmp = RAL_READ(sc, RT2860_PCI_EECTRL);
		RAL_WRITE(sc, RT2860_PCI_EECTRL, tmp | RT2860_C);
		tmp = RAL_READ(sc, RT2860_REG_SCHDMA_GPIO_CTRL_CFG);
		RAL_WRITE(sc, RT2860_REG_SCHDMA_GPIO_CTRL_CFG, tmp & ~0x0808);
	}
}

void
rt3090_rf_wakeup(struct rt2860_softc *sc)
{
	uint32_t tmp;
	uint8_t rf;

	if ((sc->mac_rev & 0xffff0000) == 0x35930000) {
		/* enable VCO */
		rf = rt3090_rf_read(sc, 1);
		rt3090_rf_write(sc, 1, rf | RT3593_VCO);

		/* initiate VCO calibration */
		rf = rt3090_rf_read(sc, 3);
		rt3090_rf_write(sc, 3, rf | RT3593_VCOCAL);

		/* enable VCO bias current control */
		rf = rt3090_rf_read(sc, 6);
		rt3090_rf_write(sc, 6, rf | RT3593_VCO_IC);

		/* initiate res calibration */
		rf = rt3090_rf_read(sc, 2);
		rt3090_rf_write(sc, 2, rf | RT3593_RESCAL);

		/* set reference current control to 0.33 mA */
		rf = rt3090_rf_read(sc, 22);
		rf &= ~RT3593_CP_IC_MASK;
		rf |= 1 << RT3593_CP_IC_SHIFT;
		rt3090_rf_write(sc, 22, rf);

		/* enable RX CTB */
		rf = rt3090_rf_read(sc, 46);
		rt3090_rf_write(sc, 46, rf | RT3593_RX_CTB);

		rf = rt3090_rf_read(sc, 20);
		rf &= ~(RT3593_LDO_RF_VC_MASK | RT3593_LDO_PLL_VC_MASK);
		rt3090_rf_write(sc, 20, rf);
	} else {
		/* enable RF block */
		rf = rt3090_rf_read(sc, 1);
		rt3090_rf_write(sc, 1, rf | RT3070_RF_BLOCK);

		/* enable VCO bias current control */
		rf = rt3090_rf_read(sc, 7);
		rt3090_rf_write(sc, 7, rf | 0x30);

		rf = rt3090_rf_read(sc, 9);
		rt3090_rf_write(sc, 9, rf | 0x0e);

		/* enable RX CTB */
		rf = rt3090_rf_read(sc, 21);
		rt3090_rf_write(sc, 21, rf | RT3070_RX_CTB);

		/* fix Tx to Rx IQ glitch by raising RF voltage */
		rf = rt3090_rf_read(sc, 27);
		rf &= ~0x77;
		if ((sc->mac_rev & 0x0000ffff) < 0x0211)
			rf |= 0x03;
		rt3090_rf_write(sc, 27, rf);
	}
	if (sc->patch_dac && (sc->mac_rev & 0x0000ffff) < 0x0211) {
		tmp = RAL_READ(sc, RT3070_LDO_CFG0);
		tmp = (tmp & ~0x1f000000) | 0x0d000000;
		RAL_WRITE(sc, RT3070_LDO_CFG0, tmp);
	}
}

int
rt3090_filter_calib(struct rt2860_softc *sc, uint8_t init, uint8_t target,
    uint8_t *val)
{
	uint8_t rf22, rf24;
	uint8_t bbp55_pb, bbp55_sb, delta;
	int ntries;

	/* program filter */
	rf24 = rt3090_rf_read(sc, 24);
	rf24 = (rf24 & 0xc0) | init;	/* initial filter value */
	rt3090_rf_write(sc, 24, rf24);

	/* enable baseband loopback mode */
	rf22 = rt3090_rf_read(sc, 22);
	rt3090_rf_write(sc, 22, rf22 | RT3070_BB_LOOPBACK);

	/* set power and frequency of passband test tone */
	rt2860_io_bbp_write(sc, 24, 0x00);
	for (ntries = 0; ntries < 100; ntries++) {
		/* transmit test tone */
		rt2860_io_bbp_write(sc, 25, 0x90);
		DELAY(1000);
		/* read received power */
		bbp55_pb = rt2860_io_bbp_read(sc, 55);
		if (bbp55_pb != 0)
			break;
	}
	if (ntries == 100)
		return ETIMEDOUT;

	/* set power and frequency of stopband test tone */
	rt2860_io_bbp_write(sc, 24, 0x06);
	for (ntries = 0; ntries < 100; ntries++) {
		/* transmit test tone */
		rt2860_io_bbp_write(sc, 25, 0x90);
		DELAY(1000);
		/* read received power */
		bbp55_sb = rt2860_io_bbp_read(sc, 55);

		delta = bbp55_pb - bbp55_sb;
		if (delta > target)
			break;

		/* reprogram filter */
		rf24++;
		rt3090_rf_write(sc, 24, rf24);
	}
	if (ntries < 100) {
		if (rf24 != init)
			rf24--;	/* backtrack */
		*val = rf24;
		rt3090_rf_write(sc, 24, rf24);
	}

	/* restore initial state */
	rt2860_io_bbp_write(sc, 24, 0x00);

	/* disable baseband loopback mode */
	rf22 = rt3090_rf_read(sc, 22);
	rt3090_rf_write(sc, 22, rf22 & ~RT3070_BB_LOOPBACK);

	return 0;
}

void
rt3090_rf_setup(struct rt2860_softc *sc)
{
	uint8_t bbp;
	int i;

	if ((sc->mac_rev & 0x0000ffff) >= 0x0211) {
		/* enable DC filter */
		rt2860_io_bbp_write(sc, 103, 0xc0);

		/* improve power consumption */
		bbp = rt2860_io_bbp_read(sc, 31);
		rt2860_io_bbp_write(sc, 31, bbp & ~0x03);
	}

	RAL_WRITE(sc, RT2860_TX_SW_CFG1, 0);
	if ((sc->mac_rev & 0x0000ffff) < 0x0211) {
		RAL_WRITE(sc, RT2860_TX_SW_CFG2,
		    sc->patch_dac ? 0x2c : 0x0f);
	} else
		RAL_WRITE(sc, RT2860_TX_SW_CFG2, 0);

	/* initialize RF registers from ROM */
	for (i = 0; i < 10; i++) {
		if (sc->rf[i].reg == 0 || sc->rf[i].reg == 0xff)
			continue;
		rt3090_rf_write(sc, sc->rf[i].reg, sc->rf[i].val);
	}
}


/*
 * rt2860_rf_set_chan
 */
void rt2860_rf_set_chan(struct rt2860_softc *sc,
	struct ieee80211_channel *c)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	uint32_t r1, r2, r3, r4;
	int8_t txpow1, txpow2;
	int i, chan;

	if (sc->mac_rev == 0x28720200) {
		rt2872_rf_set_chan(sc, c);
		return;
	}

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	/* get central channel position */

	chan = ieee80211_chan2ieee(ic, c);

	if ((sc->mac_rev & 0xffff0000) >= 0x30710000) {
		rt3090_set_chan(sc, chan);
		return;
	}

	if (IEEE80211_IS_CHAN_HT40U(c))
		chan += 2;
	else if (IEEE80211_IS_CHAN_HT40D(c))
		chan -= 2;

	RT2860_DPRINTF(sc, RT2860_DEBUG_CHAN,
		"RF set channel: channel=%u, HT%s%s\n",
		ieee80211_chan2ieee(ic, c),
		!IEEE80211_IS_CHAN_HT(c) ? " disabled" :
			IEEE80211_IS_CHAN_HT20(c) ? "20":
				IEEE80211_IS_CHAN_HT40U(c) ? "40U" : "40D",
		(ic->ic_flags & IEEE80211_F_SCAN) ? ", scanning" : "");

	if (chan == 0 || chan == IEEE80211_CHAN_ANY)
		return;

	for (i = 0; rt2860_rf2850[i].chan != chan; i++);

	r1 = rt2860_rf2850[i].r1;
	r2 = rt2860_rf2850[i].r2;
	r3 = rt2860_rf2850[i].r3;
	r4 = rt2860_rf2850[i].r4;

	txpow1 = sc->txpow1[i];
	txpow2 = sc->txpow2[i];

	if (sc->ntxpath == 1)
		r2 |= (1 << 14);

	if (sc->nrxpath == 2)
		r2 |= (1 << 6);
	else if (sc->nrxpath == 1)
		r2 |= (1 << 17) | (1 << 6);

	if (IEEE80211_IS_CHAN_2GHZ(c)) {
		r3 = (r3 & 0xffffc1ff) | (txpow1 << 9);
		r4 = (r4 & ~0x001f87c0) | (sc->rf_freq_off << 15) |
		    (txpow2 << 6);
	} else {
		r3 = r3 & 0xffffc1ff;
		r4 = (r4 & ~0x001f87c0) | (sc->rf_freq_off << 15);

		if (txpow1 >= RT2860_EEPROM_TXPOW_5GHZ_MIN && txpow1 < 0) {
			txpow1 = (-RT2860_EEPROM_TXPOW_5GHZ_MIN + txpow1);
			if (txpow1 > RT2860_EEPROM_TXPOW_5GHZ_MAX)
				txpow1 = RT2860_EEPROM_TXPOW_5GHZ_MAX;

			r3 |= (txpow1 << 10);
		} else {
			if (txpow1 > RT2860_EEPROM_TXPOW_5GHZ_MAX)
				txpow1 = RT2860_EEPROM_TXPOW_5GHZ_MAX;

			r3 |= (txpow1 << 10) | (1 << 9);
		}

		if (txpow2 >= RT2860_EEPROM_TXPOW_5GHZ_MIN && txpow2 < 0) {
			txpow2 = (-RT2860_EEPROM_TXPOW_5GHZ_MIN + txpow2);
			if (txpow2 > RT2860_EEPROM_TXPOW_5GHZ_MAX)
				txpow2 = RT2860_EEPROM_TXPOW_5GHZ_MAX;

			r4 |= (txpow2 << 7);
		} else {
			if (txpow2 > RT2860_EEPROM_TXPOW_5GHZ_MAX)
				txpow2 = RT2860_EEPROM_TXPOW_5GHZ_MAX;

			r4 |= (txpow2 << 7) | (1 << 6);
		}
	}

	if (!(ic->ic_flags & IEEE80211_F_SCAN) && IEEE80211_IS_CHAN_HT40(c))
		r4 |= (1 << 21);

	rt2860_io_rf_write(sc, RT2860_REG_RF_R1, r1);
	rt2860_io_rf_write(sc, RT2860_REG_RF_R2, r2);
	rt2860_io_rf_write(sc, RT2860_REG_RF_R3, r3 & ~(1 << 2));
	rt2860_io_rf_write(sc, RT2860_REG_RF_R4, r4);

	DELAY(200);

	rt2860_io_rf_write(sc, RT2860_REG_RF_R1, r1);
	rt2860_io_rf_write(sc, RT2860_REG_RF_R2, r2);
	rt2860_io_rf_write(sc, RT2860_REG_RF_R3, r3 | (1 << 2));
	rt2860_io_rf_write(sc, RT2860_REG_RF_R4, r4);

	DELAY(200);

	rt2860_io_rf_write(sc, RT2860_REG_RF_R1, r1);
	rt2860_io_rf_write(sc, RT2860_REG_RF_R2, r2);
	rt2860_io_rf_write(sc, RT2860_REG_RF_R3, r3 & ~(1 << 2));
	rt2860_io_rf_write(sc, RT2860_REG_RF_R4, r4);

	rt2860_rf_select_chan_group(sc, c);

	DELAY(1000);
}

/*
 * rt2872_rf_set_chan
 */
static void
rt2872_rf_set_chan(struct rt2860_softc *sc,
	struct ieee80211_channel *c)
{
	struct ifnet *ifp;
	struct ieee80211com *ic;
	uint32_t r1, r2, r3, r4;
	uint32_t r6, r7, r12, r13, r23, r24;
	int8_t txpow1, txpow2;
	int i, chan;

	ifp = sc->sc_ifp;
	ic = ifp->if_l2com;

	/* get central channel position */

	chan = ieee80211_chan2ieee(ic, c);

	if (IEEE80211_IS_CHAN_HT40U(c))
		chan += 2;
	else if (IEEE80211_IS_CHAN_HT40D(c))
		chan -= 2;

	RT2860_DPRINTF(sc, RT2860_DEBUG_CHAN,
		"RF set channel: channel=%u, HT%s%s\n",
		ieee80211_chan2ieee(ic, c),
		!IEEE80211_IS_CHAN_HT(c) ? " disabled" :
			IEEE80211_IS_CHAN_HT20(c) ? "20":
				IEEE80211_IS_CHAN_HT40U(c) ? "40U" : "40D",
		(ic->ic_flags & IEEE80211_F_SCAN) ? ", scanning" : "");

	if (chan == 0 || chan == IEEE80211_CHAN_ANY)
		return;

	for (i = 0; rt2860_rf2850[i].chan != chan; i++);

	r1 = rt2860_rf2850[i].r1;
	r2 = rt2860_rf2850[i].r2;
	r3 = rt2860_rf2850[i].r3;
	r4 = rt2860_rf2850[i].r4;

	txpow1 = sc->txpow1[i];
	txpow2 = sc->txpow2[i];

	/* Programm channel parameters */
	r2 = rt3090_freqs[i].n;
	rt2860_io_rf_write(sc, 2 , r2 );
	r3 = rt3090_freqs[i].k;
	rt2860_io_rf_write(sc, 3 , r3 );

	r6 = (rt3052_rf_default[6] & 0xFC) | (rt3090_freqs[i].r & 0x03);
	rt2860_io_rf_write(sc, 6 , r6 );

	/* Set Tx Power */
	r12 = (rt3052_rf_default[12] & 0xE0) | (txpow1 & 0x1f);
	rt2860_io_rf_write(sc, 12, r12);

	/* Set Tx1 Power */
	r13 = (rt3052_rf_default[13] & 0xE0) | (txpow2 & 0x1f);
	rt2860_io_rf_write(sc, 13, r13);

	/* Set RF offset */
	r23 = (rt3052_rf_default[23] & 0x80) | (sc->rf_freq_off);
	rt2860_io_rf_write(sc, 23, r23);

	/* Set BW */
	r24 = (rt3052_rf_default[24] & 0xDF);
	if (!(ic->ic_flags & IEEE80211_F_SCAN) && IEEE80211_IS_CHAN_HT40(c))
	    r24 |= 0x20;
	rt2860_io_rf_write(sc, 24, r24);

	/* Enable RF tuning */
	r7 = (rt3052_rf_default[7]) | 1;
	rt2860_io_rf_write(sc, 7 , r7 );

	/* Antenna */
	r1 = (rt3052_rf_default[1] & 0xab) | ((sc->nrxpath == 1)?0x10:0) |
	    ((sc->ntxpath == 1)?0x20:0);
	rt2860_io_rf_write(sc, 1 , r1 );

	DELAY(200);

	rt2860_rf_select_chan_group(sc, c);

	DELAY(1000);
}

